<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"><title>Dubbo 源码分析 – 集群容错之 LoadBalance | 云少IT</title><meta name="keywords" content="Dubbo 源码分析 – 集群容错之 LoadBalance"><meta name="author" content="云少"><meta name="copyright" content="云少"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-touch-fullscreen" content="yes"><meta name="apple-mobile-web-app-title" content="Dubbo 源码分析 – 集群容错之 LoadBalance"><meta name="application-name" content="Dubbo 源码分析 – 集群容错之 LoadBalance"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="#ffffff"><meta property="og:type" content="article"><meta property="og:title" content="Dubbo 源码分析 – 集群容错之 LoadBalance"><meta property="og:url" content="https://it985.github.io/posts/c3adb84d.html"><meta property="og:site_name" content="云少IT"><meta property="og:description" content="八、Dubbo 源码分析 – 集群容错之 LoadBalance1.简介LoadBalance 中文意思为负载均衡，它的职责是将网络请求，或者其他形式的负载“均摊”到不同的机器上。避免集群中部分服务器压力过大，而另一些服务器比较空闲的情况。通过负载均衡，可以让每台服务器获取到适合自己处理能力的负载。"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://www.bing.com/th?id=OHR.WychwoodForest_EN-GB9336729827_UHD.jpg"><meta property="article:author" content="云少"><meta property="article:tag" content="云少IT,IT,技术,分享,程序员,博客,教程,工具,框架,bug,java,spring,数据库,"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://www.bing.com/th?id=OHR.WychwoodForest_EN-GB9336729827_UHD.jpg"><meta name="description" content="八、Dubbo 源码分析 – 集群容错之 LoadBalance1.简介LoadBalance 中文意思为负载均衡，它的职责是将网络请求，或者其他形式的负载“均摊”到不同的机器上。避免集群中部分服务器压力过大，而另一些服务器比较空闲的情况。通过负载均衡，可以让每台服务器获取到适合自己处理能力的负载。"><link rel="shortcut icon" href="/img/logo.webp"><link rel="canonical" href="https://it985.github.io/posts/c3adb84d"><link rel="preconnect" href="//npm.elemecdn.com"><link rel="preconnect" href="//npm.onmicrosoft.cn"><link rel="preconnect" href="//www.google-analytics.com" crossorigin=""><link rel="preconnect" href="//busuanzi.ibruce.info"><meta name="google-site-verification" content="NuBZ4r-QCqSgo4XUScdEsQW0bolIHEiVGq4A16ndPQA"><meta name="baidu-site-verification" content="code-xxx"><meta name="msvalidate.01" content="xxx"><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.cbd.int/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/swiper/swiper.min.css" media="print" onload='this.media="all"'><script async src="https://www.googletagmanager.com/gtag/js?id=G-3VMKW5TZBM"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-3VMKW5TZBM")</script><script>const GLOBAL_CONFIG={linkPageTop:{enable:!0,title:"与数百名博主无限进步",addFriendPlaceholder:"昵称（请勿包含博客等字样）：\n网站地址（要求博客地址，请勿提交个人主页）：\n头像图片url（请提供尽可能清晰的图片，我会上传到我自己的图床）：\n描述：\n站点截图（可选）：\n"},peoplecanvas:void 0,postHeadAiDescription:{enable:!0,gptName:"云AI",mode:"tianli",switchBtn:!1,btnLink:"https://afdian.net/item/886a79d4db6711eda42a52540025c377",randomNum:3,basicWordCount:1e3,key:"48580d1e3f53ae174a1e",Referer:"https://blog.tryrun.top"},diytitle:{enable:!0,leaveTitle:"w(ﾟДﾟ)w 不要走！再看看嘛！",backTitle:"♪(^∇^*)欢迎肥来！"},LA51:{enable:!0,ck:"JiFOrFoQklEn9YLS",LingQueMonitorID:"JiqlTmdeI4e1fPbd"},greetingBox:{enable:!0,default:"晚上好👋",list:[{greeting:"晚安😴",startTime:0,endTime:5},{greeting:"早上好鸭👋, 祝你一天好心情！",startTime:6,endTime:9},{greeting:"上午好👋, 状态很好，鼓励一下～",startTime:10,endTime:10},{greeting:"11点多啦, 在坚持一下就吃饭啦～",startTime:11,endTime:11},{greeting:"午安👋, 宝贝",startTime:12,endTime:14},{greeting:"🌈充实的一天辛苦啦！",startTime:14,endTime:18},{greeting:"19点喽, 奖励一顿丰盛的大餐吧🍔。",startTime:19,endTime:19},{greeting:"晚上好👋, 在属于自己的时间好好放松😌~",startTime:20,endTime:24}]},twikooEnvId:"https://twikoo.tryrun.top/",commentBarrageConfig:void 0,root:"/",preloader:{source:2},friends_vue_info:{apiurl:"https://friends.tryrun.top/"},navMusic:!1,mainTone:void 0,authorStatus:{skills:["🤖️ 数码科技爱好者","🔍 分享与热心帮助","🏠 智能家居小能手","🔨 设计开发一条龙","🤝 专修交互与设计","🏃 脚踏实地行动派","🧱 团队小组发动机","💢 壮汉人狠话不多"]},algolia:{appId:"T5VW6VDYLS",apiKey:"227bcb041816af13cb1698db15a8ac89",indexName:"hexo-blog",hits:{per_page:6},languages:{input_placeholder:"输入关键词后按下回车查找",hits_empty:"找不到您查询的内容：${query}",hits_stats:"找到 ${hits} 条结果，用时 ${time} 毫秒"}},localSearch:void 0,translate:{defaultEncoding:2,translateDelay:0,msgToTraditionalChinese:"繁",msgToSimplifiedChinese:"简",rightMenuMsgToTraditionalChinese:"转为繁体",rightMenuMsgToSimplifiedChinese:"转为简体"},noticeOutdate:{limitDay:365,position:"top",messagePrev:"It has been",messageNext:"days since the last update, the content of the article may be outdated."},highlight:{plugin:"highlighjs",highlightCopy:!0,highlightLang:!0,highlightHeightLimit:330},copy:{success:"复制成功",error:"复制错误",noSupport:"浏览器不支持"},relativeDate:{homepage:!0,simplehomepage:!1,post:!0},runtime:"天",date_suffix:{just:"刚刚",min:"分钟前",hour:"小时前",day:"天前",month:"个月前"},copyright:void 0,lightbox:"fancybox",Snackbar:{chs_to_cht:"你已切换为繁体",cht_to_chs:"你已切换为简体",day_to_night:"你已切换为深色模式",night_to_day:"你已切换为浅色模式",bgLight:"#425AEF",bgDark:"#1f1f1f",position:"top-center"},source:{justifiedGallery:{js:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.min.js",css:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.css"}},isPhotoFigcaption:!1,islazyload:!0,isAnchor:!1,shortcutKey:void 0,autoDarkmode:!0}</script><script id="config-diff">var GLOBAL_CONFIG_SITE={configTitle:"云少IT",title:"Dubbo 源码分析 – 集群容错之 LoadBalance",postAI:"true",pageFillDescription:"八、Dubbo 源码分析 – 集群容错之 LoadBalance, 1.简介, 2.源码分析, 2.1 RandomLoadBalance, 2.2 LeastActiveLoadBalance, 2.3 ConsistentHashLoadBalance, 2.4 RoundRobinLoadBalance, 3.总结, 参考八源码分析集群容错之简介中文意思为负载均衡它的职责是将网络请求或者其他形式的负载均摊到不同的机器上避免集群中部分服务器压力过大而另一些服务器比较空闲的情况通过负载均衡可以让每台服务器获取到适合自己处理能力的负载在为高负载的服务器分流的同时还可以避免资源浪费一举两得负载均衡可分为软件负载均衡和硬件负载均衡在我们日常开发中一般很难接触到硬件负载均衡但软件负载均衡还是能够接触到一些的比如在中也有负载均衡的概念和相应的实现需要对服务消费者的调用请求进行分配避免少数服务提供者负载过大服务提供者负载过大会导致部分服务调用超时因此将负载均衡到每个服务提供者上是非常必要的提供了种负载均衡实现分别是基于权重随机算法的基于最少活跃调用数算法的基于一致性的以及基于加权轮询算法的这几个负载均衡算法代码不是很长但是想看懂也不是很容易需要大家对这几个算法的原理有一定了解才行如果不是很了解也没不用太担心我会在分析每个算法的源码之前对算法原理进行简单的讲解帮助大家建立初步的印象我在写源码分析系列文章之初当时最新的版本为近期发布了其中就有对负载均衡部分代码修改因此我在分析完版本后的源码后会另外分析更新的部分本篇文章内容非常之丰富需要大家耐心阅读好了其他的就不多说了进入正题吧源码分析在中所有负载均衡实现类均继承自该类实现了接口方法并封装了一些公共的逻辑所以在分析负载均衡实现之前先来看一下的逻辑首先来看一下负载均衡的入口方法如下如果列表中仅有一个直接返回即可无需进行负载均衡调用方法进行负载均衡该方法为抽象方法由子类实现方法的逻辑比较简单首先会检测集合的合法性然后再检测集合元素数量如果只包含一个直接返回该即可如果包含多个此时需要通过负载均衡算法选择一个具体的负载均衡算法由子类实现接下来章节会对这些子类进行详细分析除了实现了接口方法还封装了一些公共逻辑服务提供者权重计算逻辑具体实现如下从中获取配置值获取服务提供者启动时间戳计算服务提供者运行时长获取服务预热时间默认为分钟如果服务运行时间小于预热时间则重新计算服务权重即降权重新计算服务权重计算权重下面代码逻辑上形似于随着服务运行时间增大权重计算值会慢慢接近配置值上面是权重的计算过程该过程主要用于保证当服务运行时长小于服务预热时间时对服务进行降权避免让服务在启动之初就处于高负载状态服务预热是一个优化手段与此类似的还有预热主要目的是让服务启动后低功率运行一段时间使其效率慢慢提升至最佳状态关于预热方面的更多知识大家感兴趣可以自己搜索一下关于就先分析到这接下来分析各个实现类的代码首先我们从缺省的实现类看起是加权随机算法的具体实现它的算法思想很简单假设我们有一组服务器他们对应的权重为权重总和为现在把这些权重值平铺在一维坐标值上区间属于服务器区间属于服务器区间属于服务器接下来通过随机数生成器生成一个范围在之间的随机数然后计算这个随机数会落到哪个区间上比如数字会落到服务器对应的区间上此时返回服务器即可权重越大的机器在坐标轴上对应的区间范围就越大因此随机数生成器生成的数字就会有更大的概率落到此区间内只要随机数生成器产生的随机数分布性很好在经过多次选择后每个服务器被选中的次数比例接近其权重比例比如经过一万次选择后服务器被选中的次数大约为次服务器被选中的次数约为次服务器被选中的次数约为次以上就是背后的算法思想比较简单不多说了下面开始分析源码下面这个循环有两个作用第一是计算总权重第二是检测每个服务提供者的权重是否相同若不相同则将置为累加权重检测当前服务提供者的权重与上一个服务提供者的权重是否相同不相同的话则将置为下面的分支主要用于获取随机数并计算随机数落在哪个区间上随机获取一个之间的数字循环让数减去服务提供者权重值当小于时返回相应的还是用上面的例子进行说明第一次循环说明肯定不会落在服务器对应的区间上第二次循环表明落在服务器对应的区间上让随机值减去权重值返回相应的如果所有服务提供者权重值相同此时直接随机返回一个即可的算法思想比较简单在经过多次请求后能够将调用请求按照权重值进行均匀分配当然也存在一定的缺点当调用次数比较少时产生的随机数可能会比较集中此时多数请求会落到同一台服务器上这个缺点并不是很严重多数情况下可以忽略是一个简单高效的负载均衡实现因此选择它作为缺省实现关于就先到这了接下来分析翻译过来是最小活跃数负载均衡所谓的最小活跃数可理解为最少连接数即服务提供者目前正在处理的请求数一个请求对应一条连接最少表明该服务提供者效率高单位时间内可处理更多的请求此时应优先将请求分配给该服务提供者在具体实现中每个服务提供者对应一个活跃数初始情况下所有服务提供者活跃数均为每收到一个请求活跃数加完成请求后则将活跃数减在服务运行一段时间后性能好的服务提供者处理请求的速度更快因此活跃数下降的也越快此时这样的服务提供者能够优先获取到新的服务请求这就是最小活跃数负载均衡算法的基本思想除了最小活跃数在实现上还引入了权重值所以准确的来说是基于加权最小活跃数算法实现的举个例子说明一下在一个服务提供者集群中有两个性能优异的服务提供者某一时刻它们的活跃数相同此时会根据它们的权重去分配请求权重越大获取到新请求的可能性就越大如果两个服务提供者权重相同此时随机选择一个即可关于的背景知识就先介绍到这里下面开始分析源码最小的活跃数具有相同最小活跃数的服务者提供者以下用代称数量用于记录具有相同最小活跃数的在列表中的下标信息第一个最小活跃数的权重值用于与其他具有相同最小活跃数的的权重进行对比以检测是否所有具有相同最小活跃数的的权重均相等遍历列表获取对应的活跃数获取权重发现更小的活跃数重新开始使用当前活跃数更新最小活跃数更新为记录当前下标值到中当前的活跃数与最小活跃数相同在中记录下当前在集合中的下标累加权重检测当前的权重与是否相等不相等则将置为当只有一个具有最小活跃数此时直接返回该即可有多个具有相同的最小活跃数但他们的权重不同随机获取一个之间的数字循环让随机数减去具有最小活跃数的的权重值当小于等于时返回相应的获取权重值并让随机数减去权重值如果权重相同或权重为时随机返回一个如上为了帮助大家理解代码我在上面的代码中写了大量的注释下面简单总结一下以上代码所做的事情如下遍历列表寻找活跃数最小的如果有多个具有相同的最小活跃数此时记录下这些在集合中的下标以及累加它们的权重比较它们之间的权重值是否相等如果只有一个具有最小的活跃数此时直接返回该即可如果有多个具有最小活跃数且它们的权重不相等此时处理方式和一致如果有多个具有最小活跃数但它们的权重相等此时随机返回一个即可以上就是大致的实现逻辑大家在阅读的源码的过程中要注意区分活跃数与权重这两个概念不要混为一谈以上分析是基于版本进行了由于近期发布了对负载均衡部分的代码进行了一些更新这其中就包含了本节分析的所以下面简单说明一下对进行了怎样的修改回到上面的源码中我在上面的代码中标注了两个黄色的五角星两处标记对应的代码分别如下问题出在服务预热阶段第一行代码直接从中去权重值未被降权过第二行代码获取到的是经过降权后的权重第一行代码获取到的权重值最终会被累加到权重总和中这个时候会导致一个问题是一个在范围内的随机数而它所减去的是经过降权的权重很有可能在经过次运算后仍然是大于的导致无法选中这个问题对应的为在中被修复具体的修复逻辑是将标注一处的代码修改为等价于上面的变量这样命名是为了强调该变量经过降权处理了另外版本中的还要一个缺陷即当一组具有相同的最小活跃数且其中一个的权重值为此时这个无法被选中缺陷代码如下问题就出在了上举例说明假设有一组的权重为最大值为假设你会发现当循环进行第二次遍历后提前返回了此时权重为的就没有机会被选中这个修改起来也不难可以将不过的是将也就是两种改动都行不过我认为觉得第一种方式更好一点可与逻辑保持一致这里有点突兀大家读到这里要特地思考一下为什么要以上就是对的更新不是很难理解就不多说了接下来分析基于一致性思想的一致性算法由麻省理工学院的及其合作者于年提供出的算法提出之初是用于大规模缓存系统的负载均衡它的工作过程是这样的首先根据获取其他的信息为缓存节点生成一个并将这个投射到的圆环上当有查询或写入请求时则为缓存项的生成一个值然后查找第一个大于或等于该值的缓存节点并到这个节点中查询或写入缓存项如果当前节点挂了则在下一次查询或写入缓存时为缓存项查找另一个大于其值的缓存节点即可大致效果如下每个缓存节点在圆环上占据一个位置如果缓存项的的值小于缓存节点值则到该缓存节点中存储或读取缓存项比如下面绿色点对应的缓存项存储到节点中由于挂了原本应该存到该节点中的缓存想最终会存储到节点中关于一致性算法我这里只做扫盲具体的细节不讨论大家请自行补充相关的背景知识下面来看看一致性在中的应用我们把上图的缓存节点替换成的服务提供者这里相同颜色的节点均属于同一个服务提供者比如这样做的目的是通过引入虚拟节点让在圆环上分散开来避免数据倾斜问题所谓数据倾斜是指由于节点不够分散导致大量请求落到了同一个节点上而其他节点只会接收到了少量的请求比如由于和在圆环上分布不均导致系统中的请求都会落到上只有的请求会落到上解决这个问题办法是引入虚拟节点通过虚拟节点均衡各个节点的请求量到这里背景知识就普及完了接下来开始分析源码我们先从的方法开始看起如下获取原始的如果是一个新的对象意味着服务提供者数量发生了变化可能新增也可能减少了此时条件成立创建新的调用的方法选择如上方法主要做了一些前置工作比如检测列表是不是变动过以及创建这些工作做完后接下来开始调用方法执行负载均衡逻辑在分析方法之前我们先来看一下一致性选择器的初始化过程如下使用存储虚拟节点获取虚拟节点数默认为获取参与计算的参数下标值默认对第一个参数进行运算对进行运算得到一个长度为的字节数组对部分字节进行次运算得到四个不同的型正整数时取中下标为的个字节进行位运算时取中下标为的个字节进行位运算时过程同上将到的映射关系存储到中中的元素要有序因此选用作为存储结构的构造方法执行了一系列的初始化逻辑比如从配置中获取虚拟节点数以及参与计算的参数下标默认情况下只使用第一个参数进行需要特别说明的是的负载均衡逻辑只受参数值影响具有相同参数值的请求将会被分配给同一个服务提供者不权重因此使用时需要注意一下在获取虚拟节点数和参数下标配置后接下来要做的事情是计算虚拟节点值并将虚拟节点存储到中到此初始化工作就完成了接下来我们再来看看方法的逻辑将参数转为对参数进行运算取数组的前四个字节进行运算再将值传给方法寻找合适的到中查找第一个节点值大于或等于当前的如果大于在圆环上最大的位置此时需要将的头结点赋值给返回如上选择的过程比较简单了首先是对参数进行以及运算得到一个值然后再拿这个值到中查找目标即可到此关于就分析完了在阅读之前大家一定要先补充背景知识否者即使这里只有一百多行代码也很难看懂好了本节先分析到这本节我们来看一下中的加权轮询负载均衡的实现在详细分析源码前我们还是先来了解一下什么是加权轮询这里从最简单的轮询开始讲起所谓轮询就是将请求轮流分配给一组服务器举个例子我们有三台服务器我们将第一个请求分配给服务器第二个请求分配给服务器第三个请求分配给服务器第四个请求再次分配给服务器这个过程就叫做轮询轮询是一种无状态负载均衡算法实现简单适用于每台服务器性能相近的场景下显然现实情况下我们并不能保证每台服务器性能均相近如果我们将等量的请求分配给性能较差的服务器这显然是不合理的因此这个时候我们需要加权轮询算法对轮询过程进行干预使得性能好的服务器可以得到更多的请求性能差的得到的少一些每台服务器能够得到的请求数比例接近或等于他们的权重比比如服务器权重比为那么在次请求中服务器将获取到其中的次请求服务器获取到其中的次请求服务器则获取到其中的次请求以上就是加权轮询的算法思想搞懂了这个思想接下来我们就可以分析源码了我们先来看一下版本的全限定类名方法名比如最大权重最小权重权重总和下面这个循环主要用于查找最大和最小权重计算权重总和等获取最大和最小权重将封装到中累加权重查找对应的对应实例为空则创建这里可以把看成一个黑盒大家只要知道用于记录服务的调用编号即可至于细节大家如果感兴趣可以自行分析获取当前的调用编号如果最小权重最大权重表明服务提供者之间的权重是不相等的使用调用编号对权重总和进行取余操作进行次遍历遍历获取获取权重包装类如果且权重大于此时返回相应的且权重大于此时对权重和分别进行自减操作服务提供者之间的权重相等此时通过轮询选择是一个包装类主要包含了一个自减方法与不同是不可变类而是可变类省略部分代码如上的每行代码都不是很难理解但是将它们组合到一起之后好像就看不懂了这里对上面代码的主要逻辑进行总结如下找到最大权重值并计算出权重和使用调用编号对权重总和进行取余操作得到检测的值是否等于且权重是否大于如果两个条件均满足则返回该如果上面条件不满足且权重大于此时对和权重进行递减再次循环重复步骤以上过程对应的原理不太好解释所以下面直接举例说明把假设我们有三台服务器对应的权重为接下来对上面的逻辑进行简单的模拟满足条件此时直接返回服务器需要进行一次递减操作才能满足条件此时返回服务器需要进行两次递减操作才能满足条件此时返回服务器需要进行三次递减操作才能满足条件经过递减后服务器权重为此时返回服务器需要进行四次递减操作才能满足条件经过递减后服务器权重为此时返回服务器需要进行五次递减操作才能满足条件经过递减后服务器权重为此时返回服务器需要进行六次递减操作才能满足条件经过递减后服务器权重为此时返回服务器需要进行七次递减操作才能满足条件经过递减后服务器权重为此时返回服务器经过次调用后我们得到的负载均衡结果为次数比等于权重比当时此时重头再来从上面的模拟过程可以看出当后服务器就不会被选中了因为它的权重被减为了当后服务器的权重被减为此后就不会再被选中以上是版本的分析过程大家如果看不懂自己可以定义一些权重组合进行模拟也可以写点测试用例进行调试分析总之不要死看版本的存在着比较严重的性能问题该问题最初是在中被反馈出来问题出在了的返回时机上需要在条件成立的情况下才会被返回相应的假如很大比如甚至更大时方法需要进行很多次计算才能将减为由此可知的效率与有关时间复杂度为又受最大权重的影响因此当某个服务提供者配置了非常大的权重此时会产生比较严重的性能问题这个问题被反馈后社区很快做了回应并对的代码进行了重构将时间复杂度优化至了常量级别这个优化可以说很好了下面我们来学习一下优化后的代码查找最大和最小权重获取当前服务对应的调用序列对象创建默认值为获取下标序列对象创建默认值为每循环一轮重新计算检测的权重是否大于大于则返回所有权重相等此时进行普通的轮询即可上面代码的逻辑是这样的每进行一轮循环重新计算如果当前权重大于则返回该还是举例说明吧假设服务器对应权重第一轮循环可返回和第二轮循环返回第三轮循环返回第四轮循环返回第五轮循环返回如上这里的一轮循环是指再次变为所经历过的循环这里可以把看做是一轮循环的开始每一轮循环的次数与的数量有关数量通常不会太多所以我们可以认为上面代码的时间复杂度为常数级重构后的看起来已经很不错了但是在代码更新不久后很有又被重构了这次重构原因是新的在某些情况下选出的服务器序列不够均匀比如服务器对应权重现在进行次负载均衡选择出来的序列为前个请求全部都落在了服务器上分布不够均匀这将会使服务器短时间内接收大量的请求压力陡增而和无请求处于空闲状态我们期望的结果是这样的不同服务器可以穿插获取请求为了增加负载均衡结果的平滑性社区再次对的实现进行了重构这次重构参考自的平滑加权轮询负载均衡实现原理是这样的每个服务器对应两个权重分别为和其中是固定的是会动态调整初始值为当有新的请求进来时遍历服务器列表让它的加上自身权重遍历完成后找到最大的并将其减去权重总和然后返回相应的服务器即可上面描述不是很好理解下面还是举例说明吧仍然使用服务器对应权重的例子进行说明现在有个请求依次进入负载均衡逻辑选择过程如下请求编号数组选择结果减去权重总和后的数组如上经过平滑性处理后得到的服务器序列为相比之前的序列分布性要好一些初始情况下第个请求处理完后再次变为是不是很神奇这个结果也不难理解在次计算过程中每个服务器的都增加了自身权重得到被选中次要被减去和被选中次要被减去于是以上就是平滑加权轮询的计算过程现在大家应该对平滑加权轮询算法了有了一些了解接下来我们来看看是如何实现上面的计算过程的服务提供者权重当前权重最后一次更新时间初始情况下嵌套结构存储的数据结构示例如下最外层为服务类名方法名第二层为到的映射关系这里我们可以将看成是服务提供者的原子更新锁获取到映射表如果为空则创建一个新的获取当前时间下面这个循环主要做了这样几件事情遍历列表检测当前是否有对应的没有则创建检测权重是否发生了变化若变化了则更新的字段让字段加上自身权重等价于设置字段即寻找具有最大的以及暂存起来留作后用计算权重总和检测当前是否有对应的没有则创建设置权重存储唯一标识到的映射关系权重不等于中保存的权重说明权重变化了此时进行更新让加上自身权重等价于设置表示近期更新过找出最大的将具有最大权重的赋值给将对应的赋值给留作后用计算权重总和对进行检查过滤掉长时间未被更新的节点该节点可能挂了中不包含该节点所以该节点的长时间无法被更新若未更新时长超过阈值后就会被移除掉默认阈值为秒拷贝遍历修改也就是移除过期记录更新引用让减去权重总和等价于返回具有最大的以上就是版本的大家如果能够理解平滑加权轮询算法的计算过程再配合我写的注释理解上面的代码应该不难以上就是关于全部的分析内容有点多大家慢慢消化吧好了本节先到这总结本篇文章对中的几种负载均衡实现进行了详细的分析总的来说这篇文章写的还是有点累的主要是每介绍一种负载均衡实现就要介绍一下相关背景另一方面这里很多东西对于我来说也完全是新的在此之前我对负载均衡算法并没太多了解这篇文章基本上是边学边写的总共耗时天本来想简单写写算了但最后还是决定写详细点好在现在写完了我也可以放松一下了本篇文章是我的源码分析系列文章关于集群容错部分的最后一篇文章写完感觉学到了很多东西通过坚持不懈的阅读代码写技术文章使得我对有了更深入的了解当然这还远远不够后续还有很多东西要了解比如等长路漫漫步履不停好了本篇文章到这里就结束了感谢大家的阅读参考负载均衡之加权轮询算法源码预热过程简书一致性哈希算法原理博客园",isPost:!0,isHome:!1,isHighlightShrink:!0,isToc:!0,postUpdate:"2020-09-26 22:29:54",postMainColor:""}</script><noscript><style>#nav{opacity:1}.justified-gallery img{opacity:1}#post-meta time,#recent-posts time{display:inline!important}</style></noscript><script>(e=>{e.saveToLocal={set:(e,t,a)=>{var o;0!==a&&(o=Date.now(),localStorage.setItem(e,JSON.stringify({value:t,expiry:o+864e5*a})))},get:e=>{var t=localStorage.getItem(e);if(t){t=JSON.parse(t);if(!(Date.now()>t.expiry))return t.value;localStorage.removeItem(e)}}},e.getScript=(o,c={})=>new Promise((t,e)=>{const a=document.createElement("script");a.src=o,a.async=!0,a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},Object.keys(c).forEach(e=>{a.setAttribute(e,c[e])}),document.head.appendChild(a)}),e.getCSS=(o,c=!1)=>new Promise((t,e)=>{const a=document.createElement("link");a.rel="stylesheet",a.href=o,c&&(a.id=c),a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},document.head.appendChild(a)}),e.activateDarkMode=()=>{document.documentElement.setAttribute("data-theme","dark"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#0d0d0d")},e.activateLightMode=()=>{document.documentElement.setAttribute("data-theme","light"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#ffffff")};var e=saveToLocal.get("theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches,a=window.matchMedia("(prefers-color-scheme: light)").matches,o=window.matchMedia("(prefers-color-scheme: no-preference)").matches,c=!t&&!a&&!o,t=(void 0===e?(a?activateLightMode():t?activateDarkMode():(o||c)&&((a=(new Date).getHours())<=6||18<=a?activateDarkMode:activateLightMode)(),window.matchMedia("(prefers-color-scheme: dark)").addListener(e=>{void 0===saveToLocal.get("theme")&&(e.matches?activateDarkMode:activateLightMode)()})):("light"===e?activateLightMode:activateDarkMode)(),saveToLocal.get("aside-status"));void 0!==t&&("hide"===t?document.documentElement.classList.add("hide-aside"):document.documentElement.classList.remove("hide-aside"));/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)&&document.documentElement.classList.add("apple")})(window)</script><link rel="stylesheet" href="/css/1.min.css?1" media="async" onload='this.media="all"'><link rel="stylesheet" href="/css/bg.css?1" media="async" onload='this.media="all"'><meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="云少IT" type="application/atom+xml"><link rel="alternate" href="/rss.xml" title="云少IT" type="application/rss+xml"></head><body data-type="anzhiyu"><div id="web_bg"></div><div id="an_music_bg"></div><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.10/progress_bar/progress_bar.css"><script async src="https://cdn.cbd.int/pace-js@1.2.4/pace.min.js" data-pace-options="{ &quot;restartOnRequestAfter&quot;:false,&quot;eventLag&quot;:false}"></script><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><div id="nav-group"><span id="blog_name"><div class="back-home-button"><i class="anzhiyufont anzhiyu-icon-grip-vertical"></i><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div></div><a id="site-name" href="/" accesskey="h"><div class="title">云少IT</div><i class="anzhiyufont anzhiyu-icon-house-chimney"></i></a></span><div class="mask-name-container"><div id="name-container"><a id="page-name" href="javascript:anzhiyu.scrollToDest(0, 500)" rel="external nofollow noreferrer">PAGE_NAME</a></div></div><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div></div><div id="nav-right"><div class="nav-button only-home" id="travellings_button" title="随机前往一个开往项目网站"><a class="site-page" onclick="anzhiyu.totraveling()" title="随机前往一个开往项目网站" href="javascript:void(0);" rel="external nofollow" data-pjax-state="external"><i class="anzhiyufont anzhiyu-icon-train"></i></a></div><div class="nav-button" id="randomPost_button"><a class="site-page" onclick="toRandomPost()" title="随机前往一个文章" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-dice"></i></a></div><div class="nav-button" id="search-button"><a class="site-page social-icon search" href="javascript:void(0);" rel="external nofollow noreferrer" title="搜索🔍" accesskey="s"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span> 搜索</span></a></div><input id="center-console" type="checkbox"><label class="widget" for="center-console" title="中控台" onclick="anzhiyu.switchConsole()"><i class="left"></i><i class="widget center"></i><i class="widget right"></i></label><div id="console"><div class="console-card-group-reward"><ul class="reward-all console-card"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" alt="wechat" src="/img/wxpay.webp"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" alt="alipay" src="/img/alipay.webp"></a><div class="post-qr-code-desc">alipay</div></li></ul></div><div class="console-card-group"><div class="console-card-group-left"><div class="console-card" id="card-newest-comments"><div class="card-content"><div class="author-content-item-tips">互动</div><span class="author-content-item-title">最新评论</span></div><div class="aside-list"><span>正在加载中...</span></div></div></div><div class="console-card-group-right"><div class="console-card tags"><div class="card-content"><div class="author-content-item-tips">兴趣点</div><span class="author-content-item-title">寻找你感兴趣的领域</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:1.05rem;color:#637571">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:1.05rem;color:#8a0460">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:1.05rem;color:#6b3641">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:1.05rem;color:#5e6603">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:1.05rem;color:#abb61f">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:1.05rem;color:#6dc55c">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:1.05rem;color:#8745c2">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:1.05rem;color:#804042">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:1.05rem;color:#21b4c6">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:1.05rem;color:#0a3988">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:1.05rem;color:#29446d">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:1.05rem;color:#afc378">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:1.05rem;color:#b10843">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:1.05rem;color:#9663a4">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:1.05rem;color:#b4214d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:1.05rem;color:#220f01">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:1.05rem;color:#c4106c">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:1.05rem;color:#1f5f9f">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:1.05rem;color:#c3563b">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:1.05rem;color:#1a3364">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:1.05rem;color:#1ea132">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:1.05rem;color:#a00679">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:1.05rem;color:#9e5e9b">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:1.05rem;color:#4d47bc">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:1.05rem;color:#a56245">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:1.05rem;color:#9c8435">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:1.05rem;color:#647e28">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:1.05rem;color:#a192b9">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:1.05rem;color:#22a370">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:1.05rem;color:#6b985c">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:1.05rem;color:#bc5f40">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:1.05rem;color:#167366">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:1.05rem;color:#aa928f">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:1.05rem;color:#3a740b">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:1.05rem;color:#75c343">面试<sup>11</sup></a></div></div><hr></div></div><div class="console-card history"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-box-archiv"></i><span>文章</span></div><div class="card-archives"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-archive"></i><span>归档</span><a class="card-more-btn" href="/archives/" title="查看更多"> <i class="anzhiyufont anzhiyu-icon-angle-right"></i></a></div><ul class="card-archive-list"><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/06/"><span class="card-archive-list-date">六月 2023</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2021/05/"><span class="card-archive-list-date">五月 2021</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/10/"><span class="card-archive-list-date">十月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">21</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/09/"><span class="card-archive-list-date">九月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">44</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/08/"><span class="card-archive-list-date">八月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">47</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/07/"><span class="card-archive-list-date">七月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">42</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/06/"><span class="card-archive-list-date">六月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">31</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/05/"><span class="card-archive-list-date">五月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">43</span><span>篇</span></div></a></li></ul></div><hr></div></div></div><div class="button-group"><div class="console-btn-item"><a class="darkmode_switchbutton" title="显示模式切换" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-moon"></i></a></div><div class="console-btn-item" id="consoleHideAside" onclick="anzhiyu.hideAsideBtn()" title="边栏显示控制"><a class="asideSwitch"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></a></div><div class="console-btn-item on" id="consoleCommentBarrage" onclick="anzhiyu.switchCommentBarrage()" title="热评开关"><a class="commentBarrage"><i class="anzhiyufont anzhiyu-icon-message"></i></a></div><div class="console-btn-item" id="consoleMusic" onclick="anzhiyu.musicToggle()" title="音乐开关"><a class="music-switch"><i class="anzhiyufont anzhiyu-icon-music"></i></a></div></div><div class="console-mask" onclick="anzhiyu.hideConsole()" href="javascript:void(0);" rel="external nofollow noreferrer"></div></div><div class="nav-button" id="nav-totop"><a class="totopbtn" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i><span id="percent" onclick="anzhiyu.scrollToDest(0,500)">0</span></a></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer" title="切换"><i class="anzhiyufont anzhiyu-icon-bars"></i></a></div></div></div></nav><div id="post-info"><div id="post-firstinfo"><div class="meta-firstline"><a class="post-meta-original">原创</a><span class="article-meta tags"></span></div></div><h1 class="post-title" itemprop="name headline">Dubbo 源码分析 – 集群容错之 LoadBalance</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="anzhiyufont anzhiyu-icon-calendar-days post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" itemprop="dateCreated datePublished" datetime="2020-09-26T14:29:54.000Z" title="发表于 2020-09-26 22:29:54">2020-09-26</time><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-history post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" itemprop="dateCreated datePublished" datetime="2020-09-26T14:29:54.000Z" title="更新于 2020-09-26 22:29:54">2020-09-26</time></span></div><div class="meta-secondline"><span class="post-meta-separator"></span><span class="post-meta-wordcount"><i class="anzhiyufont anzhiyu-icon-file-word post-meta-icon" title="文章字数"></i><span class="post-meta-label" title="文章字数">字数总计:</span><span class="word-count" title="文章字数">10.2k</span><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-clock post-meta-icon" title="阅读时长"></i><span class="post-meta-label" title="阅读时长">阅读时长:</span><span>38分钟</span></span><span class="post-meta-separator"></span><span class="post-meta-pv-cv" data-flag-title="Dubbo 源码分析 – 集群容错之 LoadBalance"><i class="anzhiyufont anzhiyu-icon-fw-eye post-meta-icon"></i><span class="post-meta-label" title="阅读量">阅读量:</span><span id="busuanzi_value_page_pv"><i class="anzhiyufont anzhiyu-icon-spinner anzhiyu-spin"></i></span></span><span class="post-meta-separator"> </span><span class="post-meta-position" title="作者IP属地为武汉"><i class="anzhiyufont anzhiyu-icon-location-dot"></i>武汉</span></div></div></div><section class="main-hero-waves-area waves-area"><svg class="waves-svg" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"><defs><path id="gentle-wave" d="M -160 44 c 30 0 58 -18 88 -18 s 58 18 88 18 s 58 -18 88 -18 s 58 18 88 18 v 44 h -352 Z"></path></defs><g class="parallax"><use href="#gentle-wave" x="48" y="0"></use><use href="#gentle-wave" x="48" y="3"></use><use href="#gentle-wave" x="48" y="5"></use><use href="#gentle-wave" x="48" y="7"></use></g></svg></section><div id="post-top-cover"><img class="nolazyload" id="post-top-bg" src="https://www.bing.com/th?id=OHR.SealRiver_EN-GB9654795287_UHD.jpg"></div></header><main id="blog-container"><div class="layout" id="content-inner"><div id="post"><div class="post-ai-description"><div class="ai-title"><i class="anzhiyufont anzhiyu-icon-bilibili"></i><div class="ai-title-text">AI-摘要</div><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i><i class="anzhiyufont anzhiyu-icon-circle-dot" title="朗读摘要"></i><div id="ai-tag">Tianli GPT</div></div><div class="ai-explanation">AI初始化中...</div><div class="ai-btn-box"><div class="ai-btn-item">介绍自己 🙈</div><div class="ai-btn-item">生成本文简介 👋</div><div class="ai-btn-item">推荐相关文章 📖</div><div class="ai-btn-item">前往主页 🏠</div><div class="ai-btn-item" id="go-tianli-blog">前往爱发电购买</div></div><script data-pjax src="/js/anzhiyu/ai_abstract.js"></script></div><article class="post-content" id="article-container" itemscope itemtype="https://it985.github.io/posts/c3adb84d.html"><header><h1 id="CrawlerTitle" itemprop="name headline">Dubbo 源码分析 – 集群容错之 LoadBalance</h1><span itemprop="author" itemscope itemtype="http://schema.org/Person">云少</span><time itemprop="dateCreated datePublished" datetime="2020-09-26T14:29:54.000Z" title="发表于 2020-09-26 22:29:54">2020-09-26</time><time itemprop="dateCreated datePublished" datetime="2020-09-26T14:29:54.000Z" title="更新于 2020-09-26 22:29:54">2020-09-26</time></header><h1 id="八、Dubbo-源码分析-–-集群容错之-LoadBalance"><a href="#八、Dubbo-源码分析-–-集群容错之-LoadBalance" class="headerlink" title="八、Dubbo 源码分析 – 集群容错之 LoadBalance"></a>八、Dubbo 源码分析 – 集群容错之 LoadBalance</h1><h2 id="1-简介"><a href="#1-简介" class="headerlink" title="1.简介"></a>1.简介</h2><p>LoadBalance 中文意思为负载均衡，它的职责是将网络请求，或者其他形式的负载“均摊”到不同的机器上。避免集群中部分服务器压力过大，而另一些服务器比较空闲的情况。通过负载均衡，可以让每台服务器获取到适合自己处理能力的负载。在为高负载的服务器分流的同时，还可以避免资源浪费，一举两得。负载均衡可分为软件负载均衡和硬件负载均衡。在我们日常开发中，一般很难接触到硬件负载均衡。但软件负载均衡还是能够接触到一些的，比如 Nginx。在 Dubbo 中，也有负载均衡的概念和相应的实现。Dubbo 需要对服务消费者的调用请求进行分配，避免少数服务提供者负载过大。服务提供者负载过大，会导致部分服务调用超时。因此将负载均衡到每个服务提供者上，是非常必要的。Dubbo 提供了4种负载均衡实现，分别是基于权重随机算法的 RandomLoadBalance、基于最少活跃调用数算法的 LeastActiveLoadBalance、基于 hash 一致性的 ConsistentHashLoadBalance，以及基于加权轮询算法的 RoundRobinLoadBalance。这几个负载均衡算法代码不是很长，但是想看懂也不是很容易，需要大家对这几个算法的原理有一定了解才行。如果不是很了解，也没不用太担心。我会在分析每个算法的源码之前，对算法原理进行简单的讲解，帮助大家建立初步的印象。</p><p>我在写 Dubbo 源码分析系列文章之初，当时 Dubbo 最新的版本为 2.6.4。近期，Dubbo 2.6.5 发布了，其中就有对负载均衡部分代码修改。因此我在分析完 2.6.4 版本后的源码后，会另外分析 2.6.5 更新的部分。本篇文章内容非常之丰富，需要大家耐心阅读。好了，其他的就不多说了，进入正题吧。</p><h2 id="2-源码分析"><a href="#2-源码分析" class="headerlink" title="2.源码分析"></a>2.源码分析</h2><p>在 Dubbo 中，所有负载均衡实现类均继承自 AbstractLoadBalance，该类实现了 LoadBalance 接口方法，并封装了一些公共的逻辑。所以在分析负载均衡实现之前，先来看一下 AbstractLoadBalance 的逻辑。首先来看一下负载均衡的入口方法 select，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">select</span><span class="params">(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (invokers == <span class="literal">null</span> || invokers.isEmpty())</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// 如果 invokers 列表中仅有一个 Invoker，直接返回即可，无需进行负载均衡</span></span><br><span class="line">    <span class="keyword">if</span> (invokers.size() == <span class="number">1</span>)</span><br><span class="line">        <span class="keyword">return</span> invokers.get(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 调用 doSelect 方法进行负载均衡，该方法为抽象方法，由子类实现</span></span><br><span class="line">    <span class="keyword">return</span> doSelect(invokers, url, invocation);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">abstract</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">doSelect</span><span class="params">(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation)</span>;</span><br></pre></td></tr></table></figure><p>select 方法的逻辑比较简单，首先会检测 invokers 集合的合法性，然后再检测 invokers 集合元素数量。如果只包含一个 Invoker，直接返回该 Inovker 即可。如果包含多个 Invoker，此时需要通过负载均衡算法选择一个 Invoker。具体的负载均衡算法由子类实现，接下来章节会对这些子类进行详细分析。</p><p>AbstractLoadBalance 除了实现了 LoadBalance 接口方法，还封装了一些公共逻辑 —— 服务提供者权重计算逻辑。具体实现如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">getWeight</span><span class="params">(Invoker&lt;?&gt; invoker, Invocation invocation)</span> &#123;</span><br><span class="line">    <span class="comment">// 从 url 中获取 weight 配置值</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">weight</span> <span class="operator">=</span> invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);</span><br><span class="line">    <span class="keyword">if</span> (weight &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 获取服务提供者启动时间戳</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">timestamp</span> <span class="operator">=</span> invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, <span class="number">0L</span>);</span><br><span class="line">        <span class="keyword">if</span> (timestamp &gt; <span class="number">0L</span>) &#123;</span><br><span class="line">            <span class="comment">// 计算服务提供者运行时长</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">uptime</span> <span class="operator">=</span> (<span class="type">int</span>) (System.currentTimeMillis() - timestamp);</span><br><span class="line">            <span class="comment">// 获取服务预热时间，默认为10分钟</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">warmup</span> <span class="operator">=</span> invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);</span><br><span class="line">            <span class="comment">// 如果服务运行时间小于预热时间，则重新计算服务权重，即降权</span></span><br><span class="line">            <span class="keyword">if</span> (uptime &gt; <span class="number">0</span> &amp;&amp; uptime &lt; warmup) &#123;</span><br><span class="line">                <span class="comment">// 重新计算服务权重</span></span><br><span class="line">                weight = calculateWarmupWeight(uptime, warmup, weight);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> weight;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="type">int</span> <span class="title function_">calculateWarmupWeight</span><span class="params">(<span class="type">int</span> uptime, <span class="type">int</span> warmup, <span class="type">int</span> weight)</span> &#123;</span><br><span class="line">    <span class="comment">// 计算权重，下面代码逻辑上形似于 (uptime / warmup) * weight。</span></span><br><span class="line">    <span class="comment">// 随着服务运行时间 uptime 增大，权重计算值 ww 会慢慢接近配置值 weight</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">ww</span> <span class="operator">=</span> (<span class="type">int</span>) ((<span class="type">float</span>) uptime / ((<span class="type">float</span>) warmup / (<span class="type">float</span>) weight));</span><br><span class="line">    <span class="keyword">return</span> ww &lt; <span class="number">1</span> ? <span class="number">1</span> : (ww &gt; weight ? weight : ww);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面是权重的计算过程，该过程主要用于保证当服务运行时长小于服务预热时间时，对服务进行降权，避免让服务在启动之初就处于高负载状态。服务预热是一个优化手段，与此类似的还有 JVM 预热。主要目的是让服务启动后“低功率”运行一段时间，使其效率慢慢提升至最佳状态。关于预热方面的更多知识，大家感兴趣可以自己搜索一下。</p><p>关于 AbstractLoadBalance 就先分析到这，接下来分析各个实现类的代码。首先，我们从 Dubbo 缺省的实现类 RandomLoadBalance 看起。</p><h3 id="2-1-RandomLoadBalance"><a href="#2-1-RandomLoadBalance" class="headerlink" title="2.1 RandomLoadBalance"></a>2.1 RandomLoadBalance</h3><p>RandomLoadBalance 是加权随机算法的具体实现，它的算法思想很简单。假设我们有一组服务器 servers &#x3D; [A, B, C]，他们对应的权重为 weights &#x3D; [5, 3, 2]，权重总和为10。现在把这些权重值平铺在一维坐标值上，[0, 5) 区间属于服务器 A，[5, 8) 区间属于服务器 B，[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数，然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上，此时返回服务器 A 即可。权重越大的机器，在坐标轴上对应的区间范围就越大，因此随机数生成器生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好，在经过多次选择后，每个服务器被选中的次数比例接近其权重比例。比如，经过一万次选择后，服务器 A 被选中的次数大约为5000次，服务器 B 被选中的次数约为3000次，服务器 C 被选中的次数约为2000次。</p><p>以上就是 RandomLoadBalance 背后的算法思想，比较简单，不多说了，下面开始分析源码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RandomLoadBalance</span> <span class="keyword">extends</span> <span class="title class_">AbstractLoadBalance</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">NAME</span> <span class="operator">=</span> <span class="string">&quot;random&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Random</span> <span class="variable">random</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">doSelect</span><span class="params">(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> invokers.size();</span><br><span class="line">        <span class="type">int</span> <span class="variable">totalWeight</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">sameWeight</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">// 下面这个循环有两个作用，第一是计算总权重 totalWeight，</span></span><br><span class="line">        <span class="comment">// 第二是检测每个服务提供者的权重是否相同，若不相同，则将 sameWeight 置为 false</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">weight</span> <span class="operator">=</span> getWeight(invokers.get(i), invocation);</span><br><span class="line">            <span class="comment">// 累加权重</span></span><br><span class="line">            totalWeight += weight;</span><br><span class="line">            <span class="comment">// 检测当前服务提供者的权重与上一个服务提供者的权重是否相同，</span></span><br><span class="line">            <span class="comment">// 不相同的话，则将 sameWeight 置为 false。</span></span><br><span class="line">            <span class="keyword">if</span> (sameWeight &amp;&amp; i &gt; <span class="number">0</span></span><br><span class="line">                    &amp;&amp; weight != getWeight(invokers.get(i - <span class="number">1</span>), invocation)) &#123;</span><br><span class="line">                sameWeight = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 下面的 if 分支主要用于获取随机数，并计算随机数落在哪个区间上</span></span><br><span class="line">        <span class="keyword">if</span> (totalWeight &gt; <span class="number">0</span> &amp;&amp; !sameWeight) &#123;</span><br><span class="line">            <span class="comment">// 随机获取一个 [0, totalWeight) 之间的数字</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">offset</span> <span class="operator">=</span> random.nextInt(totalWeight);</span><br><span class="line">            <span class="comment">// 循环让 offset 数减去服务提供者权重值，当 offset 小于0时，返回相应的 Invoker。</span></span><br><span class="line">            <span class="comment">// 还是用上面的例子进行说明，servers = [A, B, C]，weights = [5, 3, 2]，offset = 7。</span></span><br><span class="line">            <span class="comment">// 第一次循环，offset - 5 = 2 &gt; 0，说明 offset 肯定不会落在服务器 A 对应的区间上。</span></span><br><span class="line">            <span class="comment">// 第二次循环，offset - 3 = -1 &lt; 0，表明 offset 落在服务器 B 对应的区间上</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">                <span class="comment">// 让随机值 offset 减去权重值</span></span><br><span class="line">                offset -= getWeight(invokers.get(i), invocation);</span><br><span class="line">                <span class="keyword">if</span> (offset &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="comment">// 返回相应的 Invoker</span></span><br><span class="line">                    <span class="keyword">return</span> invokers.get(i);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果所有服务提供者权重值相同，此时直接随机返回一个即可</span></span><br><span class="line">        <span class="keyword">return</span> invokers.get(random.nextInt(length));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>RandomLoadBalance 的算法思想比较简单，在经过多次请求后，能够将调用请求按照权重值进行“均匀”分配。当然 RandomLoadBalance 也存在一定的缺点，当调用次数比较少时，Random 产生的随机数可能会比较集中，此时多数请求会落到同一台服务器上。这个缺点并不是很严重，多数情况下可以忽略。RandomLoadBalance 是一个简单，高效的负载均衡实现，因此 Dubbo 选择它作为缺省实现。</p><p>关于 RandomLoadBalance 就先到这了，接下来分析 LeastActiveLoadBalance。</p><h3 id="2-2-LeastActiveLoadBalance"><a href="#2-2-LeastActiveLoadBalance" class="headerlink" title="2.2 LeastActiveLoadBalance"></a>2.2 LeastActiveLoadBalance</h3><p>LeastActiveLoadBalance 翻译过来是最小活跃数负载均衡，所谓的最小活跃数可理解为最少连接数。即服务提供者目前正在处理的请求数（一个请求对应一条连接）最少，表明该服务提供者效率高，单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者。在具体实现中，每个服务提供者对应一个活跃数 active。初始情况下，所有服务提供者活跃数均为0。每收到一个请求，活跃数加1，完成请求后则将活跃数减1。在服务运行一段时间后，性能好的服务提供者处理请求的速度更快，因此活跃数下降的也越快。此时这样的服务提供者能够优先获取到新的服务请求，这就是最小活跃数负载均衡算法的基本思想。除了最小活跃数，LeastActiveLoadBalance 在实现上还引入了权重值。所以准确的来说，LeastActiveLoadBalance 是基于加权最小活跃数算法实现的。举个例子说明一下，在一个服务提供者集群中，有两个性能优异的服务提供者。某一时刻它们的活跃数相同，此时 Dubbo 会根据它们的权重去分配请求，权重越大，获取到新请求的可能性就越大。如果两个服务提供者权重相同，此时随机选择一个即可。关于 LeastActiveLoadBalance 的背景知识就先介绍到这里，下面开始分析源码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LeastActiveLoadBalance</span> <span class="keyword">extends</span> <span class="title class_">AbstractLoadBalance</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">NAME</span> <span class="operator">=</span> <span class="string">&quot;leastactive&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Random</span> <span class="variable">random</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">doSelect</span><span class="params">(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> invokers.size();</span><br><span class="line">        <span class="comment">// 最小的活跃数</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">leastActive</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">        <span class="comment">// 具有相同“最小活跃数”的服务者提供者（以下用 Invoker 代称）数量</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">leastCount</span> <span class="operator">=</span> <span class="number">0</span>; </span><br><span class="line">        <span class="comment">// leastIndexs 用于记录具有相同“最小活跃数”的 Invoker 在 invokers 列表中的下标信息</span></span><br><span class="line">        <span class="type">int</span>[] leastIndexs = <span class="keyword">new</span> <span class="title class_">int</span>[length];</span><br><span class="line">        <span class="type">int</span> <span class="variable">totalWeight</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 第一个最小活跃数的 Invoker 权重值，用于与其他具有相同最小活跃数的 Invoker 的权重进行对比，</span></span><br><span class="line">        <span class="comment">// 以检测是否所有具有相同最小活跃数的 Invoker 的权重均相等</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">firstWeight</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">sameWeight</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 遍历 invokers 列表</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">            Invoker&lt;T&gt; invoker = invokers.get(i);</span><br><span class="line">            <span class="comment">// 获取 Invoker 对应的活跃数</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">active</span> <span class="operator">=</span> RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();</span><br><span class="line">            <span class="comment">// 获取权重 - ⭐️</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">weight</span> <span class="operator">=</span> invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);</span><br><span class="line">            <span class="comment">// 发现更小的活跃数，重新开始</span></span><br><span class="line">            <span class="keyword">if</span> (leastActive == -<span class="number">1</span> || active &lt; leastActive) &#123;</span><br><span class="line">                <span class="comment">// 使用当前活跃数 active 更新最小活跃数 leastActive</span></span><br><span class="line">                leastActive = active;</span><br><span class="line">                <span class="comment">// 更新 leastCount 为 1</span></span><br><span class="line">                leastCount = <span class="number">1</span>;</span><br><span class="line">                <span class="comment">// 记录当前下标值到 leastIndexs 中</span></span><br><span class="line">                leastIndexs[<span class="number">0</span>] = i;</span><br><span class="line">                totalWeight = weight;</span><br><span class="line">                firstWeight = weight;</span><br><span class="line">                sameWeight = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 当前 Invoker 的活跃数 active 与最小活跃数 leastActive 相同 </span></span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (active == leastActive) &#123;</span><br><span class="line">                <span class="comment">// 在 leastIndexs 中记录下当前 Invoker 在 invokers 集合中的下标</span></span><br><span class="line">                leastIndexs[leastCount++] = i;</span><br><span class="line">                <span class="comment">// 累加权重</span></span><br><span class="line">                totalWeight += weight;</span><br><span class="line">                <span class="comment">// 检测当前 Invoker 的权重与 firstWeight 是否相等，</span></span><br><span class="line">                <span class="comment">// 不相等则将 sameWeight 置为 false</span></span><br><span class="line">                <span class="keyword">if</span> (sameWeight &amp;&amp; i &gt; <span class="number">0</span></span><br><span class="line">                    &amp;&amp; weight != firstWeight) &#123;</span><br><span class="line">                    sameWeight = <span class="literal">false</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 当只有一个 Invoker 具有最小活跃数，此时直接返回该 Invoker 即可</span></span><br><span class="line">        <span class="keyword">if</span> (leastCount == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> invokers.get(leastIndexs[<span class="number">0</span>]);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 有多个 Invoker 具有相同的最小活跃数，但他们的权重不同</span></span><br><span class="line">        <span class="keyword">if</span> (!sameWeight &amp;&amp; totalWeight &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 随机获取一个 [0, totalWeight) 之间的数字</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">offsetWeight</span> <span class="operator">=</span> random.nextInt(totalWeight);</span><br><span class="line">            <span class="comment">// 循环让随机数减去具有最小活跃数的 Invoker 的权重值，</span></span><br><span class="line">            <span class="comment">// 当 offset 小于等于0时，返回相应的 Invoker</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; leastCount; i++) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">leastIndex</span> <span class="operator">=</span> leastIndexs[i];</span><br><span class="line">                <span class="comment">// 获取权重值，并让随机数减去权重值 - ⭐️</span></span><br><span class="line">                offsetWeight -= getWeight(invokers.get(leastIndex), invocation);</span><br><span class="line">                <span class="keyword">if</span> (offsetWeight &lt;= <span class="number">0</span>)</span><br><span class="line">                    <span class="keyword">return</span> invokers.get(leastIndex);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 如果权重相同或权重为0时，随机返回一个 Invoker</span></span><br><span class="line">        <span class="keyword">return</span> invokers.get(leastIndexs[random.nextInt(leastCount)]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，为了帮助大家理解代码，我在上面的代码中写了大量的注释。下面简单总结一下以上代码所做的事情，如下：</p><ol><li>遍历 invokers 列表，寻找活跃数最小的 Invoker</li><li>如果有多个 Invoker 具有相同的最小活跃数，此时记录下这些 Invoker 在 invokers 集合中的下标，以及累加它们的权重，比较它们之间的权重值是否相等</li><li>如果只有一个 Invoker 具有最小的活跃数，此时直接返回该 Invoker 即可</li><li>如果有多个 Invoker 具有最小活跃数，且它们的权重不相等，此时处理方式和 RandomLoadBalance 一致</li><li>如果有多个 Invoker 具有最小活跃数，但它们的权重相等，此时随机返回一个即可</li></ol><p>以上就是 LeastActiveLoadBalance 大致的实现逻辑，大家在阅读的源码的过程中要注意区分活跃数与权重这两个概念，不要混为一谈。</p><p>以上分析是基于 Dubbo 2.6.4 版本进行了，由于近期 Dubbo 2.6.5 发布了，对负载均衡部分的代码进行了一些更新。这其中就包含了本节分析的 LeastActiveLoadBalance，所以下面简单说明一下 Dubbo 2.6.5 对 LeastActiveLoadBalance 进行了怎样的修改。回到上面的源码中，我在上面的代码中标注了两个黄色的五角星⭐️。两处标记对应的代码分别如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">weight</span> <span class="operator">=</span> invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);</span><br><span class="line">offsetWeight -= getWeight(invokers.get(leastIndex), invocation);</span><br></pre></td></tr></table></figure><p>问题出在服务预热阶段，第一行代码直接从 url 中去权重值，未被降权过。第二行代码获取到的是经过降权后的权重。第一行代码获取到的权重值最终会被累加到权重总和 totalWeight 中，这个时候会导致一个问题。offsetWeight 是一个在 [0, totalWeight) 范围内的随机数，而它所减去的是经过降权的权重。很有可能在经过 leastCount 次运算后，offsetWeight 仍然是大于0的，导致无法选中 Invoker。这个问题对应的 issue 为 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/apache/incubator-dubbo/issues/904">#904</a>，在 pull request <a target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/apache/incubator-dubbo/pull/2172">#2172</a> 中被修复。具体的修复逻辑是将标注一处的代码修改为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// afterWarmup 等价于上面的 weight 变量，这样命名是为了强调该变量经过 warmup 降权处理了</span></span><br><span class="line"><span class="type">int</span> <span class="variable">afterWarmup</span> <span class="operator">=</span> getWeight(invoker, invocation);</span><br></pre></td></tr></table></figure><p>另外，2.6.4 版本中的 LeastActiveLoadBalance 还要一个缺陷，即当一组 Invoker 具有相同的最小活跃数，且其中一个 Invoker 的权重值为1，此时这个 Invoker 无法被选中。缺陷代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">offsetWeight</span> <span class="operator">=</span> random.nextInt(totalWeight);</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; leastCount; i++) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">leastIndex</span> <span class="operator">=</span> leastIndexs[i];</span><br><span class="line">    offsetWeight -= getWeight(invokers.get(leastIndex), invocation);</span><br><span class="line">    <span class="keyword">if</span> (offsetWeight &lt;= <span class="number">0</span>)    <span class="comment">// ❌</span></span><br><span class="line">        <span class="keyword">return</span> invokers.get(leastIndex);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>问题就出在了<code>offsetWeight &lt;= 0</code>上，举例说明，假设有一组 Invoker 的权重为 5、2、1，offsetWeight 最大值为 7。假设 offsetWeight &#x3D; 7，你会发现，当 for 循环进行第二次遍历后 offsetWeight &#x3D; 7 – 5 – 2 &#x3D; 0，提前返回了。此时，权重为1的 Invoker 就没有机会被选中。这个修改起来也不难，可以将 <code>offsetWeight &lt; 0</code>，不过 Dubbo 的是将<code>offsetWeight + 1</code>，也就是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">offsetWeight</span> <span class="operator">=</span> random.nextInt(totalWeight) + <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>两种改动都行，不过我认为觉得第一种方式更好一点，可与 RandomLoadBalance 逻辑保持一致。这里+1有点突兀，大家读到这里要特地思考一下为什么要+1。</p><p>以上就是 Dubob 2.6.5 对 LeastActiveLoadBalance 的更新，不是很难理解，就不多说了。接下来分析基于一致性 hash 思想的 ConsistentHashLoadBalance。</p><h3 id="2-3-ConsistentHashLoadBalance"><a href="#2-3-ConsistentHashLoadBalance" class="headerlink" title="2.3 ConsistentHashLoadBalance"></a>2.3 ConsistentHashLoadBalance</h3><p>一致性 hash 算法由麻省理工学院的 Karger 及其合作者于1997年提供出的，算法提出之初是用于大规模缓存系统的负载均衡。它的工作过程是这样的，首先根据 ip 获取其他的信息为缓存节点生成一个 hash，并将这个 hash 投射到 [0, 232 – 1] 的圆环上。当有查询或写入请求时，则为缓存项的 key 生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点，并到这个节点中查询或写入缓存项。如果当前节点挂了，则在下一次查询或写入缓存时，为缓存项查找另一个大于其 hash 值的缓存节点即可。大致效果如下，每个缓存节点在圆环上占据一个位置。如果缓存项的 key 的 hash 值小于缓存节点 hash 值，则到该缓存节点中存储或读取缓存项。比如下面绿色点对应的缓存项存储到 cache-2 节点中。由于 cache-3 挂了，原本应该存到该节点中的缓存想最终会存储到 cache-4 节点中。</p><p>关于一致性 hash 算法，我这里只做扫盲。具体的细节不讨论，大家请自行补充相关的背景知识。下面来看看一致性 hash 在 Dubbo 中的应用。我们把上图的缓存节点替换成 Dubbo 的服务提供者</p><p>这里相同颜色的节点均属于同一个服务提供者，比如 Invoker1-1，Invoker1-2，……, Invoker1-160。这样做的目的是通过引入虚拟节点，让 Invoker 在圆环上分散开来，避免数据倾斜问题。所谓数据倾斜是指，由于节点不够分散，导致大量请求落到了同一个节点上，而其他节点只会接收到了少量的请求。比如：由于 Invoker-1 和 Invoker-2 在圆环上分布不均，导致系统中75%的请求都会落到 Invoker-1 上，只有 25% 的请求会落到 Invoker-2 上。解决这个问题办法是引入虚拟节点，通过虚拟节点均衡各个节点的请求量。</p><p>到这里背景知识就普及完了，接下来开始分析源码。我们先从 ConsistentHashLoadBalance 的 doSelect 方法开始看起，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConsistentHashLoadBalance</span> <span class="keyword">extends</span> <span class="title class_">AbstractLoadBalance</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConcurrentMap&lt;String, ConsistentHashSelector&lt;?&gt;&gt; selectors = </span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;String, ConsistentHashSelector&lt;?&gt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">doSelect</span><span class="params">(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">methodName</span> <span class="operator">=</span> RpcUtils.getMethodName(invocation);</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> invokers.get(<span class="number">0</span>).getUrl().getServiceKey() + <span class="string">&quot;.&quot;</span> + methodName;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取 invokers 原始的 hashcode</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">identityHashCode</span> <span class="operator">=</span> System.identityHashCode(invokers);</span><br><span class="line">        ConsistentHashSelector&lt;T&gt; selector = (ConsistentHashSelector&lt;T&gt;) selectors.get(key);</span><br><span class="line">        <span class="comment">// 如果 invokers 是一个新的 List 对象，意味着服务提供者数量发生了变化，可能新增也可能减少了。</span></span><br><span class="line">        <span class="comment">// 此时 selector.identityHashCode != identityHashCode 条件成立</span></span><br><span class="line">        <span class="keyword">if</span> (selector == <span class="literal">null</span> || selector.identityHashCode != identityHashCode) &#123;</span><br><span class="line">            <span class="comment">// 创建新的 ConsistentHashSelector</span></span><br><span class="line">            selectors.put(key, <span class="keyword">new</span> <span class="title class_">ConsistentHashSelector</span>&lt;T&gt;(invokers, methodName, identityHashCode));</span><br><span class="line">            selector = (ConsistentHashSelector&lt;T&gt;) selectors.get(key);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 调用 ConsistentHashSelector 的 select 方法选择 Invoker</span></span><br><span class="line">        <span class="keyword">return</span> selector.select(invocation);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ConsistentHashSelector</span>&lt;T&gt; &#123;...&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，doSelect 方法主要做了一些前置工作，比如检测 invokers 列表是不是变动过，以及创建 ConsistentHashSelector。这些工作做完后，接下来开始调用 select 方法执行负载均衡逻辑。在分析 select 方法之前，我们先来看一下一致性 hash 选择器 ConsistentHashSelector 的初始化过程，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ConsistentHashSelector</span>&lt;T&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用 TreeMap 存储 Invoker 虚拟节点</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> TreeMap&lt;Long, Invoker&lt;T&gt;&gt; virtualInvokers;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> replicaNumber;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> identityHashCode;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span>[] argumentIndex;</span><br><span class="line"></span><br><span class="line">    ConsistentHashSelector(List&lt;Invoker&lt;T&gt;&gt; invokers, String methodName, <span class="type">int</span> identityHashCode) &#123;</span><br><span class="line">        <span class="built_in">this</span>.virtualInvokers = <span class="keyword">new</span> <span class="title class_">TreeMap</span>&lt;Long, Invoker&lt;T&gt;&gt;();</span><br><span class="line">        <span class="built_in">this</span>.identityHashCode = identityHashCode;</span><br><span class="line">        <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> invokers.get(<span class="number">0</span>).getUrl();</span><br><span class="line">        <span class="comment">// 获取虚拟节点数，默认为160</span></span><br><span class="line">        <span class="built_in">this</span>.replicaNumber = url.getMethodParameter(methodName, <span class="string">&quot;hash.nodes&quot;</span>, <span class="number">160</span>);</span><br><span class="line">        <span class="comment">// 获取参与 hash 计算的参数下标值，默认对第一个参数进行 hash 运算</span></span><br><span class="line">        String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, <span class="string">&quot;hash.arguments&quot;</span>, <span class="string">&quot;0&quot;</span>));</span><br><span class="line">        argumentIndex = <span class="keyword">new</span> <span class="title class_">int</span>[index.length];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; index.length; i++) &#123;</span><br><span class="line">            argumentIndex[i] = Integer.parseInt(index[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (Invoker&lt;T&gt; invoker : invokers) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">address</span> <span class="operator">=</span> invoker.getUrl().getAddress();</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; replicaNumber / <span class="number">4</span>; i++) &#123;</span><br><span class="line">                <span class="comment">// 对 address + i 进行 md5 运算，得到一个长度为16的字节数组</span></span><br><span class="line">                <span class="type">byte</span>[] digest = md5(address + i);</span><br><span class="line">                <span class="comment">// 对 digest 部分字节进行4次 hash 运算，得到四个不同的 long 型正整数</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> <span class="number">0</span>; h &lt; <span class="number">4</span>; h++) &#123;</span><br><span class="line">                    <span class="comment">// h = 0 时，取 digest 中下标为 0 ~ 3 的4个字节进行位运算</span></span><br><span class="line">                    <span class="comment">// h = 1 时，取 digest 中下标为 4 ~ 7 的4个字节进行位运算</span></span><br><span class="line">                    <span class="comment">// h = 2, h = 3 时过程同上</span></span><br><span class="line">                    <span class="type">long</span> <span class="variable">m</span> <span class="operator">=</span> hash(digest, h);</span><br><span class="line">                    <span class="comment">// 将 hash 到 invoker 的映射关系存储到 virtualInvokers 中，</span></span><br><span class="line">                    <span class="comment">// virtualInvokers 中的元素要有序，因此选用 TreeMap 作为存储结构</span></span><br><span class="line">                    virtualInvokers.put(m, invoker);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ConsistentHashSelector 的构造方法执行了一系列的初始化逻辑，比如从配置中获取虚拟节点数以及参与 hash 计算的参数下标，默认情况下只使用第一个参数进行 hash。需要特别说明的是，ConsistentHashLoadBalance 的负载均衡逻辑只受参数值影响，具有相同参数值的请求将会被分配给同一个服务提供者。ConsistentHashLoadBalance 不 care 权重，因此使用时需要注意一下。</p><p>在获取虚拟节点数和参数下标配置后，接下来要做的事情是计算虚拟节点 hash 值，并将虚拟节点存储到 TreeMap 中。到此，ConsistentHashSelector 初始化工作就完成了。接下来，我们再来看看 select 方法的逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Invoker&lt;T&gt; <span class="title function_">select</span><span class="params">(Invocation invocation)</span> &#123;</span><br><span class="line">    <span class="comment">// 将参数转为 key</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> toKey(invocation.getArguments());</span><br><span class="line">    <span class="comment">// 对参数 key 进行 md5 运算</span></span><br><span class="line">    <span class="type">byte</span>[] digest = md5(key);</span><br><span class="line">    <span class="comment">// 取 digest 数组的前四个字节进行 hash 运算，再将 hash 值传给 selectForKey 方法，</span></span><br><span class="line">    <span class="comment">// 寻找合适的 Invoker</span></span><br><span class="line">    <span class="keyword">return</span> selectForKey(hash(digest, <span class="number">0</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Invoker&lt;T&gt; <span class="title function_">selectForKey</span><span class="params">(<span class="type">long</span> hash)</span> &#123;</span><br><span class="line">    <span class="comment">// 到 TreeMap 中查找第一个节点值大于或等于当前 hash 的 Invoker</span></span><br><span class="line">    Map.Entry&lt;Long, Invoker&lt;T&gt;&gt; entry = virtualInvokers.tailMap(hash, <span class="literal">true</span>).firstEntry();</span><br><span class="line">    <span class="comment">// 如果 hash 大于 Invoker 在圆环上最大的位置，此时 entry = null，</span></span><br><span class="line">    <span class="comment">// 需要将 TreeMap 的头结点赋值给 entry</span></span><br><span class="line">    <span class="keyword">if</span> (entry == <span class="literal">null</span>) &#123;</span><br><span class="line">        entry = virtualInvokers.firstEntry();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回 Invoker</span></span><br><span class="line">    <span class="keyword">return</span> entry.getValue();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，选择的过程比较简单了。首先是对参数进行 md5 以及 hash 运算，得到一个 hash 值。然后再拿这个值到 TreeMap 中查找目标 Invoker 即可。</p><p>到此关于 ConsistentHashLoadBalance 就分析完了。在阅读 ConsistentHashLoadBalance 之前，大家一定要先补充背景知识。否者即使这里只有一百多行代码，也很难看懂。好了，本节先分析到这。</p><h3 id="2-4-RoundRobinLoadBalance"><a href="#2-4-RoundRobinLoadBalance" class="headerlink" title="2.4 RoundRobinLoadBalance"></a>2.4 RoundRobinLoadBalance</h3><p>本节，我们来看一下 Dubbo 中的加权轮询负载均衡的实现 RoundRobinLoadBalance。在详细分析源码前，我们还是先来了解一下什么是加权轮询。这里从最简单的轮询开始讲起，所谓轮询就是将请求轮流分配给一组服务器。举个例子，我们有三台服务器 A、B、C。我们将第一个请求分配给服务器 A，第二个请求分配给服务器 B，第三个请求分配给服务器 C，第四个请求再次分配给服务器 A。这个过程就叫做轮询。轮询是一种无状态负载均衡算法，实现简单，适用于每台服务器性能相近的场景下。显然，现实情况下，我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给性能较差的服务器，这显然是不合理的。因此，这个时候我们需要加权轮询算法，对轮询过程进行干预，使得性能好的服务器可以得到更多的请求，性能差的得到的少一些。每台服务器能够得到的请求数比例，接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中，服务器 A 将获取到其中的5次请求，服务器 B 获取到其中的2次请求，服务器 C 则获取到其中的1次请求。</p><p>以上就是加权轮询的算法思想，搞懂了这个思想，接下来我们就可以分析源码了。我们先来看一下 2.6.4 版本的 RoundRobinLoadBalance。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RoundRobinLoadBalance</span> <span class="keyword">extends</span> <span class="title class_">AbstractLoadBalance</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">NAME</span> <span class="operator">=</span> <span class="string">&quot;roundrobin&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConcurrentMap&lt;String, AtomicPositiveInteger&gt; sequences = </span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;String, AtomicPositiveInteger&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">doSelect</span><span class="params">(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation)</span> &#123;</span><br><span class="line">        <span class="comment">// key = 全限定类名 + &quot;.&quot; + 方法名，比如 com.xxx.DemoService.sayHello</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> invokers.get(<span class="number">0</span>).getUrl().getServiceKey() + <span class="string">&quot;.&quot;</span> + invocation.getMethodName();</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> invokers.size();</span><br><span class="line">        <span class="comment">// 最大权重</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">maxWeight</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 最小权重</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">minWeight</span> <span class="operator">=</span> Integer.MAX_VALUE;</span><br><span class="line">        <span class="keyword">final</span> LinkedHashMap&lt;Invoker&lt;T&gt;, IntegerWrapper&gt; invokerToWeightMap = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span>&lt;Invoker&lt;T&gt;, IntegerWrapper&gt;();</span><br><span class="line">        <span class="comment">// 权重总和</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">weightSum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 下面这个循环主要用于查找最大和最小权重，计算权重总和等</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">weight</span> <span class="operator">=</span> getWeight(invokers.get(i), invocation);</span><br><span class="line">            <span class="comment">// 获取最大和最小权重</span></span><br><span class="line">            maxWeight = Math.max(maxWeight, weight);</span><br><span class="line">            minWeight = Math.min(minWeight, weight);</span><br><span class="line">            <span class="keyword">if</span> (weight &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 将 weight 封装到 IntegerWrapper 中</span></span><br><span class="line">                invokerToWeightMap.put(invokers.get(i), <span class="keyword">new</span> <span class="title class_">IntegerWrapper</span>(weight));</span><br><span class="line">                <span class="comment">// 累加权重</span></span><br><span class="line">                weightSum += weight;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 查找 key 对应的对应 AtomicPositiveInteger 实例，为空则创建。</span></span><br><span class="line">        <span class="comment">// 这里可以把 AtomicPositiveInteger 看成一个黑盒，大家只要知道</span></span><br><span class="line">        <span class="comment">// AtomicPositiveInteger 用于记录服务的调用编号即可。至于细节，</span></span><br><span class="line">        <span class="comment">// 大家如果感兴趣，可以自行分析</span></span><br><span class="line">        <span class="type">AtomicPositiveInteger</span> <span class="variable">sequence</span> <span class="operator">=</span> sequences.get(key);</span><br><span class="line">        <span class="keyword">if</span> (sequence == <span class="literal">null</span>) &#123;</span><br><span class="line">            sequences.putIfAbsent(key, <span class="keyword">new</span> <span class="title class_">AtomicPositiveInteger</span>());</span><br><span class="line">            sequence = sequences.get(key);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取当前的调用编号</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">currentSequence</span> <span class="operator">=</span> sequence.getAndIncrement();</span><br><span class="line">        <span class="comment">// 如果 最小权重 &lt; 最大权重，表明服务提供者之间的权重是不相等的</span></span><br><span class="line">        <span class="keyword">if</span> (maxWeight &gt; <span class="number">0</span> &amp;&amp; minWeight &lt; maxWeight) &#123;</span><br><span class="line">            <span class="comment">// 使用调用编号对权重总和进行取余操作</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">mod</span> <span class="operator">=</span> currentSequence % weightSum;</span><br><span class="line">            <span class="comment">// 进行 maxWeight 次遍历</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; maxWeight; i++) &#123;</span><br><span class="line">                <span class="comment">// 遍历 invokerToWeightMap</span></span><br><span class="line">                <span class="keyword">for</span> (Map.Entry&lt;Invoker&lt;T&gt;, IntegerWrapper&gt; each : invokerToWeightMap.entrySet()) &#123;</span><br><span class="line">                    <span class="comment">// 获取 Invoker</span></span><br><span class="line">                    <span class="keyword">final</span> Invoker&lt;T&gt; k = each.getKey();</span><br><span class="line">                    <span class="comment">// 获取权重包装类 IntegerWrapper</span></span><br><span class="line">                    <span class="keyword">final</span> <span class="type">IntegerWrapper</span> <span class="variable">v</span> <span class="operator">=</span> each.getValue();</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 如果 mod = 0，且权重大于0，此时返回相应的 Invoker</span></span><br><span class="line">                    <span class="keyword">if</span> (mod == <span class="number">0</span> &amp;&amp; v.getValue() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                        <span class="keyword">return</span> k;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// mod != 0，且权重大于0，此时对权重和 mod 分别进行自减操作</span></span><br><span class="line">                    <span class="keyword">if</span> (v.getValue() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                        v.decrement();</span><br><span class="line">                        mod--;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 服务提供者之间的权重相等，此时通过轮询选择 Invoker</span></span><br><span class="line">        <span class="keyword">return</span> invokers.get(currentSequence % length);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// IntegerWrapper 是一个 int 包装类，主要包含了一个自减方法。</span></span><br><span class="line">    <span class="comment">// 与 Integer 不同，Integer 是不可变类，而 IntegerWrapper 是可变类</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">IntegerWrapper</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> value;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">decrement</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.value--;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 省略部分代码</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，RoundRobinLoadBalance 的每行代码都不是很难理解，但是将它们组合到一起之后，好像就看不懂了。这里对上面代码的主要逻辑进行总结，如下：</p><ol><li>找到最大权重值，并计算出权重和</li><li>使用调用编号对权重总和进行取余操作，得到 mod</li><li>检测 mod 的值是否等于0，且 Invoker 权重是否大于0，如果两个条件均满足，则返回该 Invoker</li><li>如果上面条件不满足，且 Invoker 权重大于0，此时对 mod 和权重进行递减</li><li>再次循环，重复步骤3、4</li></ol><p>以上过程对应的原理不太好解释，所以下面直接举例说明把。假设我们有三台服务器 servers &#x3D; [A, B, C]，对应的权重为 weights &#x3D; [2, 5, 1]。接下来对上面的逻辑进行简单的模拟。</p><p>mod &#x3D; 0：满足条件，此时直接返回服务器 A</p><p>mod &#x3D; 1：需要进行一次递减操作才能满足条件，此时返回服务器 B</p><p>mod &#x3D; 2：需要进行两次递减操作才能满足条件，此时返回服务器 C</p><p>mod &#x3D; 3：需要进行三次递减操作才能满足条件，经过递减后，服务器权重为 [1, 4, 0]，此时返回服务器 A</p><p>mod &#x3D; 4：需要进行四次递减操作才能满足条件，经过递减后，服务器权重为 [0, 4, 0]，此时返回服务器 B</p><p>mod &#x3D; 5：需要进行五次递减操作才能满足条件，经过递减后，服务器权重为 [0, 3, 0]，此时返回服务器 B</p><p>mod &#x3D; 6：需要进行六次递减操作才能满足条件，经过递减后，服务器权重为 [0, 2, 0]，此时返回服务器 B</p><p>mod &#x3D; 7：需要进行七次递减操作才能满足条件，经过递减后，服务器权重为 [0, 1, 0]，此时返回服务器 B</p><p>经过8次调用后，我们得到的负载均衡结果为 [A, B, C, A, B, B, B, B]，次数比 A:B:C &#x3D; 2:5:1，等于权重比。当 sequence &#x3D; 8 时，mod &#x3D; 0，此时重头再来。从上面的模拟过程可以看出，当 mod &gt;&#x3D; 3 后，服务器 C 就不会被选中了，因为它的权重被减为0了。当 mod &gt;&#x3D; 4 后，服务器 A 的权重被减为0，此后 A 就不会再被选中。</p><p>以上是 2.6.4 版本的 RoundRobinLoadBalance 分析过程，大家如果看不懂，自己可以定义一些权重组合进行模拟。也可以写点测试用例，进行调试分析，总之不要死看。</p><p>2.6.4 版本的 RoundRobinLoadBalance 存在着比较严重的性能问题，该问题最初是在 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/apache/incubator-dubbo/issues/2578">issue #2578</a> 中被反馈出来。问题出在了 Invoker 的返回时机上，RoundRobinLoadBalance 需要在<code>mod == 0 &amp;&amp; v.getValue() &gt; 0</code> 条件成立的情况下才会被返回相应的 Invoker。假如 mod 很大，比如 10000，50000，甚至更大时，doSelect 方法需要进行很多次计算才能将 mod 减为0。由此可知，doSelect 的效率与 mod 有关，时间复杂度为 O(mod)。mod 又受最大权重 maxWeight 的影响，因此当某个服务提供者配置了非常大的权重，此时 RoundRobinLoadBalance 会产生比较严重的性能问题。这个问题被反馈后，社区很快做了回应。并对 RoundRobinLoadBalance 的代码进行了重构，将时间复杂度优化至了常量级别。这个优化可以说很好了，下面我们来学习一下优化后的代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RoundRobinLoadBalance</span> <span class="keyword">extends</span> <span class="title class_">AbstractLoadBalance</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">NAME</span> <span class="operator">=</span> <span class="string">&quot;roundrobin&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConcurrentMap&lt;String, AtomicPositiveInteger&gt; sequences = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;String, AtomicPositiveInteger&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConcurrentMap&lt;String, AtomicPositiveInteger&gt; indexSeqs = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;String, AtomicPositiveInteger&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">doSelect</span><span class="params">(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> invokers.get(<span class="number">0</span>).getUrl().getServiceKey() + <span class="string">&quot;.&quot;</span> + invocation.getMethodName();</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> invokers.size();</span><br><span class="line">        <span class="type">int</span> <span class="variable">maxWeight</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">minWeight</span> <span class="operator">=</span> Integer.MAX_VALUE;</span><br><span class="line">        <span class="keyword">final</span> List&lt;Invoker&lt;T&gt;&gt; invokerToWeightList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 查找最大和最小权重</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">weight</span> <span class="operator">=</span> getWeight(invokers.get(i), invocation);</span><br><span class="line">            maxWeight = Math.max(maxWeight, weight);</span><br><span class="line">            minWeight = Math.min(minWeight, weight);</span><br><span class="line">            <span class="keyword">if</span> (weight &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                invokerToWeightList.add(invokers.get(i));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取当前服务对应的调用序列对象 AtomicPositiveInteger</span></span><br><span class="line">        <span class="type">AtomicPositiveInteger</span> <span class="variable">sequence</span> <span class="operator">=</span> sequences.get(key);</span><br><span class="line">        <span class="keyword">if</span> (sequence == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 创建 AtomicPositiveInteger，默认值为0</span></span><br><span class="line">            sequences.putIfAbsent(key, <span class="keyword">new</span> <span class="title class_">AtomicPositiveInteger</span>());</span><br><span class="line">            sequence = sequences.get(key);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取下标序列对象 AtomicPositiveInteger</span></span><br><span class="line">        <span class="type">AtomicPositiveInteger</span> <span class="variable">indexSeq</span> <span class="operator">=</span> indexSeqs.get(key);</span><br><span class="line">        <span class="keyword">if</span> (indexSeq == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 创建 AtomicPositiveInteger，默认值为 -1</span></span><br><span class="line">            indexSeqs.putIfAbsent(key, <span class="keyword">new</span> <span class="title class_">AtomicPositiveInteger</span>(-<span class="number">1</span>));</span><br><span class="line">            indexSeq = indexSeqs.get(key);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (maxWeight &gt; <span class="number">0</span> &amp;&amp; minWeight &lt; maxWeight) &#123;</span><br><span class="line">            length = invokerToWeightList.size();</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> indexSeq.incrementAndGet() % length;</span><br><span class="line">                <span class="type">int</span> <span class="variable">currentWeight</span> <span class="operator">=</span> sequence.get() % maxWeight;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 每循环一轮（index = 0），重新计算 currentWeight</span></span><br><span class="line">                <span class="keyword">if</span> (index == <span class="number">0</span>) &#123;</span><br><span class="line">                    currentWeight = sequence.incrementAndGet() % maxWeight;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 检测 Invoker 的权重是否大于 currentWeight，大于则返回</span></span><br><span class="line">                <span class="keyword">if</span> (getWeight(invokerToWeightList.get(index), invocation) &gt; currentWeight) &#123;</span><br><span class="line">                    <span class="keyword">return</span> invokerToWeightList.get(index);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 所有 Invoker 权重相等，此时进行普通的轮询即可</span></span><br><span class="line">        <span class="keyword">return</span> invokers.get(sequence.incrementAndGet() % length);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码的逻辑是这样的，每进行一轮循环，重新计算 currentWeight。如果当前 Invoker 权重大于 currentWeight，则返回该 Invoker。还是举例说明吧，假设服务器 [A, B, C] 对应权重 [5, 2, 1]。</p><p>第一轮循环，currentWeight &#x3D; 1，可返回 A 和 B</p><p>第二轮循环，currentWeight &#x3D; 2，返回 A</p><p>第三轮循环，currentWeight &#x3D; 3，返回 A</p><p>第四轮循环，currentWeight &#x3D; 4，返回 A</p><p>第五轮循环，currentWeight &#x3D; 0，返回 A, B, C</p><p>如上，这里的一轮循环是指 index 再次变为0所经历过的循环，这里可以把 index &#x3D; 0 看做是一轮循环的开始。每一轮循环的次数与 Invoker 的数量有关，Invoker 数量通常不会太多，所以我们可以认为上面代码的时间复杂度为常数级。</p><p>重构后的 RoundRobinLoadBalance 看起来已经很不错了，但是在代码更新不久后，很有又被重构了。这次重构原因是新的 RoundRobinLoadBalance 在某些情况下选出的服务器序列不够均匀。比如，服务器 [A, B, C] 对应权重 [5, 1, 1]。现在进行7次负载均衡，选择出来的序列为 [A, A, A, A, A, B, C]。前5个请求全部都落在了服务器 A上，分布不够均匀。这将会使服务器 A 短时间内接收大量的请求，压力陡增。而 B 和 C 无请求，处于空闲状态。我们期望的结果是这样的 [A, A, B, A, C, A, A]，不同服务器可以穿插获取请求。为了增加负载均衡结果的平滑性，社区再次对 RoundRobinLoadBalance 的实现进行了重构。这次重构参考自 Nginx 的平滑加权轮询负载均衡，实现原理是这样的。每个服务器对应两个权重，分别为 weight 和 currentWeight。其中 weight 是固定的，currentWeight 是会动态调整，初始值为0。当有新的请求进来时，遍历服务器列表，让它的 currentWeight 加上自身权重。遍历完成后，找到最大的 currentWeight，并将其减去权重总和，然后返回相应的服务器即可。</p><p>上面描述不是很好理解，下面还是举例说明吧。仍然使用服务器 [A, B, C] 对应权重 [5, 1, 1] 的例子进行说明，现在有7个请求依次进入负载均衡逻辑，选择过程如下：</p><table><thead><tr><th align="center">请求编号</th><th align="center">currentWeight 数组</th><th align="center">选择结果</th><th align="center">减去权重总和后的 currentWeight 数组</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">[5, 1, 1]</td><td align="center">A</td><td align="center">[-2, 1, 1]</td></tr><tr><td align="center">2</td><td align="center">[3, 2, 2]</td><td align="center">A</td><td align="center">[-4, 2, 2]</td></tr><tr><td align="center">3</td><td align="center">[1, 3, 3]</td><td align="center">B</td><td align="center">[1, -4, 3]</td></tr><tr><td align="center">4</td><td align="center">[6, -3, 4]</td><td align="center">A</td><td align="center">[-1, -3, 4]</td></tr><tr><td align="center">5</td><td align="center">[4, -2, 5]</td><td align="center">C</td><td align="center">[4, -2, -2]</td></tr><tr><td align="center">6</td><td align="center">[9, -1, -1]</td><td align="center">A</td><td align="center">[2, -1, -1]</td></tr><tr><td align="center">7</td><td align="center">[7, 0, 0]</td><td align="center">A</td><td align="center">[0, 0, 0]</td></tr></tbody></table><p>如上，经过平滑性处理后，得到的服务器序列为 [A, A, B, A, C, A, A]，相比之前的序列 [A, A, A, A, A, B, C]，分布性要好一些。初始情况下 currentWeight &#x3D; [0, 0, 0]，第7个请求处理完后，currentWeight 再次变为 [0, 0, 0]，是不是很神奇。这个结果也不难理解，在7次计算过程中，每个服务器的 currentWeight 都增加了自身权重 weight * 7，得到 currentWeight &#x3D; [35, 7, 7]，A 被选中5次，要被减去 5 * 7。B 和 C 被选中1次，要被减去 1 * 7。于是 currentWeight &#x3D; [35, 7, 7] – [35, 7, 7] &#x3D; [0, 0, 0]。</p><p>以上就是平滑加权轮询的计算过程，现在大家应该对平滑加权轮询算法了有了一些了解。接下来，我们来看看 Dubbo-2.6.5 是如何实现上面的计算过程的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RoundRobinLoadBalance</span> <span class="keyword">extends</span> <span class="title class_">AbstractLoadBalance</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">NAME</span> <span class="operator">=</span> <span class="string">&quot;roundrobin&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">RECYCLE_PERIOD</span> <span class="operator">=</span> <span class="number">60000</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">WeightedRoundRobin</span> &#123;</span><br><span class="line">        <span class="comment">// 服务提供者权重</span></span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> weight;</span><br><span class="line">        <span class="comment">// 当前权重</span></span><br><span class="line">        <span class="keyword">private</span> <span class="type">AtomicLong</span> <span class="variable">current</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicLong</span>(<span class="number">0</span>);</span><br><span class="line">        <span class="comment">// 最后一次更新时间</span></span><br><span class="line">        <span class="keyword">private</span> <span class="type">long</span> lastUpdate;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setWeight</span><span class="params">(<span class="type">int</span> weight)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.weight = weight;</span><br><span class="line">            <span class="comment">// 初始情况下，current = 0</span></span><br><span class="line">            current.set(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">increaseCurrent</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="comment">// current = current + weight；</span></span><br><span class="line">            <span class="keyword">return</span> current.addAndGet(weight);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sel</span><span class="params">(<span class="type">int</span> total)</span> &#123;</span><br><span class="line">            <span class="comment">// current = current - total;</span></span><br><span class="line">            current.addAndGet(-<span class="number">1</span> * total);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 嵌套 Map 结构，存储的数据结构示例如下：</span></span><br><span class="line">    <span class="comment">// &#123;</span></span><br><span class="line">    <span class="comment">//     &quot;UserService.query&quot;: &#123;</span></span><br><span class="line">    <span class="comment">//         &quot;url1&quot;: WeightedRoundRobin@123, </span></span><br><span class="line">    <span class="comment">//         &quot;url2&quot;: WeightedRoundRobin@456, </span></span><br><span class="line">    <span class="comment">//     &#125;,</span></span><br><span class="line">    <span class="comment">//     &quot;UserService.update&quot;: &#123;</span></span><br><span class="line">    <span class="comment">//         &quot;url1&quot;: WeightedRoundRobin@123, </span></span><br><span class="line">    <span class="comment">//         &quot;url2&quot;: WeightedRoundRobin@456,</span></span><br><span class="line">    <span class="comment">//     &#125;</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line">    <span class="comment">// 最外层为服务类名 + 方法名，第二层为 url 到 WeightedRoundRobin 的映射关系。</span></span><br><span class="line">    <span class="comment">// 这里我们可以将 url 看成是服务提供者的 id</span></span><br><span class="line">    <span class="keyword">private</span> ConcurrentMap&lt;String, ConcurrentMap&lt;String, WeightedRoundRobin&gt;&gt; methodWeightMap = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;String, ConcurrentMap&lt;String, WeightedRoundRobin&gt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 原子更新锁</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">AtomicBoolean</span> <span class="variable">updateLock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicBoolean</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">doSelect</span><span class="params">(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> invokers.get(<span class="number">0</span>).getUrl().getServiceKey() + <span class="string">&quot;.&quot;</span> + invocation.getMethodName();</span><br><span class="line">        <span class="comment">// 获取 url 到 WeightedRoundRobin 映射表，如果为空，则创建一个新的</span></span><br><span class="line">        ConcurrentMap&lt;String, WeightedRoundRobin&gt; map = methodWeightMap.get(key);</span><br><span class="line">        <span class="keyword">if</span> (map == <span class="literal">null</span>) &#123;</span><br><span class="line">            methodWeightMap.putIfAbsent(key, <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;String, WeightedRoundRobin&gt;());</span><br><span class="line">            map = methodWeightMap.get(key);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> <span class="variable">totalWeight</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">long</span> <span class="variable">maxCurrent</span> <span class="operator">=</span> Long.MIN_VALUE;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取当前时间</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        Invoker&lt;T&gt; selectedInvoker = <span class="literal">null</span>;</span><br><span class="line">        <span class="type">WeightedRoundRobin</span> <span class="variable">selectedWRR</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 下面这个循环主要做了这样几件事情：</span></span><br><span class="line">        <span class="comment">//   1. 遍历 Invoker 列表，检测当前 Invoker 是否有</span></span><br><span class="line">        <span class="comment">//      对应的 WeightedRoundRobin，没有则创建</span></span><br><span class="line">        <span class="comment">//   2. 检测 Invoker 权重是否发生了变化，若变化了，</span></span><br><span class="line">        <span class="comment">//      则更新 WeightedRoundRobin 的 weight 字段</span></span><br><span class="line">        <span class="comment">//   3. 让 current 字段加上自身权重，等价于 current += weight</span></span><br><span class="line">        <span class="comment">//   4. 设置 lastUpdate 字段，即 lastUpdate = now</span></span><br><span class="line">        <span class="comment">//   5. 寻找具有最大 current 的 Invoker 以及 WeightedRoundRobin，</span></span><br><span class="line">        <span class="comment">//      暂存起来，留作后用</span></span><br><span class="line">        <span class="comment">//   6. 计算权重总和</span></span><br><span class="line">        <span class="keyword">for</span> (Invoker&lt;T&gt; invoker : invokers) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">identifyString</span> <span class="operator">=</span> invoker.getUrl().toIdentityString();</span><br><span class="line">            <span class="type">WeightedRoundRobin</span> <span class="variable">weightedRoundRobin</span> <span class="operator">=</span> map.get(identifyString);</span><br><span class="line">            <span class="type">int</span> <span class="variable">weight</span> <span class="operator">=</span> getWeight(invoker, invocation);</span><br><span class="line">            <span class="keyword">if</span> (weight &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                weight = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 检测当前 Invoker 是否有对应的 WeightedRoundRobin，没有则创建</span></span><br><span class="line">            <span class="keyword">if</span> (weightedRoundRobin == <span class="literal">null</span>) &#123;</span><br><span class="line">                weightedRoundRobin = <span class="keyword">new</span> <span class="title class_">WeightedRoundRobin</span>();</span><br><span class="line">                <span class="comment">// 设置 Invoker 权重</span></span><br><span class="line">                weightedRoundRobin.setWeight(weight);</span><br><span class="line">                <span class="comment">// 存储 url 唯一标识 identifyString 到 weightedRoundRobin 的映射关系</span></span><br><span class="line">                map.putIfAbsent(identifyString, weightedRoundRobin);</span><br><span class="line">                weightedRoundRobin = map.get(identifyString);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// Invoker 权重不等于 WeightedRoundRobin 中保存的权重，说明权重变化了，此时进行更新</span></span><br><span class="line">            <span class="keyword">if</span> (weight != weightedRoundRobin.getWeight()) &#123;</span><br><span class="line">                weightedRoundRobin.setWeight(weight);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 让 current 加上自身权重，等价于 current += weight</span></span><br><span class="line">            <span class="type">long</span> <span class="variable">cur</span> <span class="operator">=</span> weightedRoundRobin.increaseCurrent();</span><br><span class="line">            <span class="comment">// 设置 lastUpdate，表示近期更新过</span></span><br><span class="line">            weightedRoundRobin.setLastUpdate(now);</span><br><span class="line">            <span class="comment">// 找出最大的 current </span></span><br><span class="line">            <span class="keyword">if</span> (cur &gt; maxCurrent) &#123;</span><br><span class="line">                maxCurrent = cur;</span><br><span class="line">                <span class="comment">// 将具有最大 current 权重的 Invoker 赋值给 selectedInvoker</span></span><br><span class="line">                selectedInvoker = invoker;</span><br><span class="line">                <span class="comment">// 将 Invoker 对应的 weightedRoundRobin 赋值给 selectedWRR，留作后用</span></span><br><span class="line">                selectedWRR = weightedRoundRobin;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 计算权重总和</span></span><br><span class="line">            totalWeight += weight;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 对 &lt;identifyString, WeightedRoundRobin&gt; 进行检查，过滤掉长时间未被更新的节点。</span></span><br><span class="line">        <span class="comment">// 该节点可能挂了，invokers 中不包含该节点，所以该节点的 lastUpdate 长时间无法被更新。</span></span><br><span class="line">        <span class="comment">// 若未更新时长超过阈值后，就会被移除掉，默认阈值为60秒。</span></span><br><span class="line">        <span class="keyword">if</span> (!updateLock.get() &amp;&amp; invokers.size() != map.size()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (updateLock.compareAndSet(<span class="literal">false</span>, <span class="literal">true</span>)) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    ConcurrentMap&lt;String, WeightedRoundRobin&gt; newMap = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;String, WeightedRoundRobin&gt;();</span><br><span class="line">                    <span class="comment">// 拷贝</span></span><br><span class="line">                    newMap.putAll(map);</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 遍历修改，也就是移除过期记录</span></span><br><span class="line">                    Iterator&lt;Entry&lt;String, WeightedRoundRobin&gt;&gt; it = newMap.entrySet().iterator();</span><br><span class="line">                    <span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">                        Entry&lt;String, WeightedRoundRobin&gt; item = it.next();</span><br><span class="line">                        <span class="keyword">if</span> (now - item.getValue().getLastUpdate() &gt; RECYCLE_PERIOD) &#123;</span><br><span class="line">                            it.remove();</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 更新引用</span></span><br><span class="line">                    methodWeightMap.put(key, newMap);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    updateLock.set(<span class="literal">false</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (selectedInvoker != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 让 current 减去权重总和，等价于 current -= totalWeight</span></span><br><span class="line">            selectedWRR.sel(totalWeight);</span><br><span class="line">            <span class="comment">// 返回具有最大 current 的 Invoker</span></span><br><span class="line">            <span class="keyword">return</span> selectedInvoker;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// should not happen here</span></span><br><span class="line">        <span class="keyword">return</span> invokers.get(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上就是 Dubbo-2.6.5 版本的 RoundRobinLoadBalance，大家如果能够理解平滑加权轮询算法的计算过程，再配合我写的注释，理解上面的代码应该不难。</p><p>以上就是关于 RoundRobinLoadBalance 全部的分析，内容有点多，大家慢慢消化吧。好了，本节先到这。</p><h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3.总结"></a>3.总结</h2><p>本篇文章对 Dubbo 中的几种负载均衡实现进行了详细的分析，总的来说，这篇文章写的还是有点累的。主要是每介绍一种负载均衡实现，就要介绍一下相关背景。另一方面，这里很多东西对于我来说，也完全是新的。在此之前，我对负载均衡算法并没太多了解。这篇文章基本上是边学边写的，总共耗时5天。本来想简单写写算了，但最后还是决定写详细点。好在，现在写完了，我也可以放松一下了。</p><p>本篇文章是我的 Dubbo 源码分析系列文章关于集群容错部分的最后一篇文章，写完感觉学到了很多东西。通过坚持不懈的阅读代码，写技术文章，使得我对 Dubbo 有了更深入的了解。当然，这还远远不够。后续还有很多东西要了解，比如 Nacos、Sentinel 等。长路漫漫，步履不停。</p><p>好了，本篇文章到这里就结束了。感谢大家的阅读。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a target="_blank" rel="noopener external nofollow noreferrer" href="https://blog.csdn.net/gqtcgq/article/details/52076997">负载均衡之加权轮询算法 – CSDN</a></li><li><a target="_blank" rel="noopener external nofollow noreferrer" href="https://www.jianshu.com/p/8e31a3f02e80">dubbo源码-预热warmup过程 – 简书</a></li><li><a target="_blank" rel="noopener external nofollow noreferrer" href="https://www.cnblogs.com/lpfuture/p/5796398.html">一致性哈希算法原理 – 博客园</a></li></ul></article><div class="post-copyright"><div class="copyright-cc-box"><i class="anzhiyufont anzhiyu-icon-copyright"></i></div><div class="post-copyright__author_box"><a class="post-copyright__author_img" href="/" title="头像"><img class="post-copyright__author_img_back" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"><img class="post-copyright__author_img_front" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"></a><div class="post-copyright__author_name">云少</div><div class="post-copyright__author_desc">站在巨人的肩膀罢了</div></div><div class="post-copyright__post__info"><a class="post-copyright__original" title="该文章为原创文章，注意版权协议" href="https://it985.github.io/posts/c3adb84d.html">原创</a><a class="post-copyright-title"><span onclick='rm.copyPageUrl("https://it985.github.io/posts/c3adb84d.html")'>Dubbo 源码分析 – 集群容错之 LoadBalance</span></a></div><div class="post-tools" id="post-tools"><div class="post-tools-left"><div class="rewardLeftButton"><div class="post-reward" onclick="anzhiyu.addRewardMask()"><div class="reward-button button--animated" title="赞赏作者"><i class="anzhiyufont anzhiyu-icon-hand-heart-fill"></i>打赏作者</div><div class="reward-main"><div class="reward-all"><span class="reward-title">感谢你赐予我前进的力量</span><ul class="reward-group"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/wxpay.webp" alt="wechat"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/alipay.webp" alt="alipay"></a><div class="post-qr-code-desc">alipay</div></li></ul><a class="reward-main-btn" href="/about/#about-reward" target="_blank"><div class="reward-text">赞赏者名单</div><div class="reward-dec">因为你们的支持让我意识到写文章的价值🙏</div></a></div></div></div><div id="quit-box" onclick="anzhiyu.removeRewardMask()" style="display:none"></div></div><div class="shareRight"><div class="share-link mobile"><div class="share-qrcode"><div class="share-button" title="使用手机访问这篇文章"><i class="anzhiyufont anzhiyu-icon-qrcode"></i></div><div class="share-main"><div class="share-main-all"><div id="qrcode" title="https://it985.github.io/posts/c3adb84d.html"></div><div class="reward-dec">使用手机访问这篇文章</div></div></div></div></div><div class="share-link weibo"><a class="share-button" target="_blank" href="https://service.weibo.com/share/share.php?title=Dubbo 源码分析 – 集群容错之 LoadBalance&amp;url=https://it985.github.io/posts/c3adb84d.html&amp;pic=https://www.bing.com/th?id=OHR.SealRiver_EN-GB9654795287_UHD.jpg" rel="external nofollow noreferrer noopener"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a></div><div class="share-link copyurl"><div class="share-button" id="post-share-url" title="复制链接" onclick="rm.copyPageUrl()"><i class="anzhiyufont anzhiyu-icon-link"></i></div></div></div></div></div><div class="post-copyright__notice"><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://it985.github.io" target="_blank">云少IT</a>！</span></div></div><div class="post-tools-right"><div class="tag_share"><div class="post-meta__box"><div class="post-meta__box__tag-list"></div></div><div class="post_share"><div class="social-share" data-image="https://img02.anheyu.com/adminuploads/1/2022/09/05/6315e146a8bbd.webp" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/css/share.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/js/social-share.min.js" defer></script></div></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/posts/e743e3e1.html"><img class="prev-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.AmboseliBioshere_EN-GB3709239548_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Dubbo 源码分析 – 集群容错之 Cluster</div></div></a></div><div class="next-post pull-right"><a href="/posts/2ad0f5ed.html"><img class="next-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.MandelaCamden_EN-GB1953677756_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Dubbo 源码分析 – 服务调用过程</div></div></a></div></nav><hr><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="anzhiyufont anzhiyu-icon-comments"></i><span> 评论</span></div><div class="comment-randomInfo"><a onclick="anzhiyu.addRandomCommentInfo()" href="javascript:void(0)" rel="external nofollow noreferrer">匿名评论</a><a href="/privacy" style="margin-left:4px">隐私政策</a></div><div class="comment-switch"><span class="first-comment">Twikoo</span><span id="switch-btn"></span><span class="second-comment">Artalk</span></div><div class="comment-tips" id="comment-tips"><span>✅ 你无需删除空行，直接评论以获取最佳展示效果</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div><div><div id="artalk-wrap"></div></div></div></div><div class="comment-barrage"></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-bars"></i><span>文章目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%85%AB%E3%80%81Dubbo-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E2%80%93-%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%E4%B9%8B-LoadBalance"><span class="toc-number">1.</span> <span class="toc-text">八、Dubbo 源码分析 – 集群容错之 LoadBalance</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#1-%E7%AE%80%E4%BB%8B"><span class="toc-number">1.1.</span> <span class="toc-text">1.简介</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90"><span class="toc-number">1.2.</span> <span class="toc-text">2.源码分析</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1-RandomLoadBalance"><span class="toc-number">1.2.1.</span> <span class="toc-text">2.1 RandomLoadBalance</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-LeastActiveLoadBalance"><span class="toc-number">1.2.2.</span> <span class="toc-text">2.2 LeastActiveLoadBalance</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-3-ConsistentHashLoadBalance"><span class="toc-number">1.2.3.</span> <span class="toc-text">2.3 ConsistentHashLoadBalance</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-4-RoundRobinLoadBalance"><span class="toc-number">1.2.4.</span> <span class="toc-text">2.4 RoundRobinLoadBalance</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-%E6%80%BB%E7%BB%93"><span class="toc-number">1.3.</span> <span class="toc-text">3.总结</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83"><span class="toc-number">1.4.</span> <span class="toc-text">参考</span></a></li></ol></li></ol></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div id="footer_deal"><a class="deal_link" href="mailto:2071916845@qq.com" rel="external nofollow noreferrer" title="email"><i class="anzhiyufont anzhiyu-icon-envelope"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://weibo.com/" title="微博"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.facebook.com/" title="facebook"><i class="anzhiyufont anzhiyu-icon-facebook1"></i></a><a class="deal_link" href="/atom.xml" title="RSS"><i class="anzhiyufont anzhiyu-icon-rss"></i></a><img class="footer_mini_logo" title="返回顶部" alt="返回顶部" onclick="anzhiyu.scrollToDest(0,500)" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" size="50px"><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/it985" title="Github"><i class="anzhiyufont anzhiyu-icon-github"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://space.bilibili.com/300767383" title="Bilibili"><i class="anzhiyufont anzhiyu-icon-bilibili"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://v.douyin.com/" title="抖音"><i class="anzhiyufont anzhiyu-icon-tiktok"></i></a><a class="deal_link" href="/copyright" title="CC"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i></a></div><div id="anzhiyu-footer"><div class="footer-group"><div class="footer-title">服务</div><div class="footer-links"><a class="footer-item" title="51la统计" target="_blank" rel="noopener external nofollow noreferrer" href="https://v6.51.la/">51la统计</a><a class="footer-item" title="十年之约" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.foreverblog.cn/">十年之约</a><a class="footer-item" title="开往" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/travellings-link/travellings">开往</a></div></div><div class="footer-group"><div class="footer-title">导航</div><div class="footer-links"><a class="footer-item" title="即刻短文" href="/essay/">即刻短文</a><a class="footer-item" title="友链文章" href="/fcircle/">友链文章</a><a class="footer-item" title="留言板" href="/comments/">留言板</a></div></div><div class="footer-group"><div class="footer-title">协议</div><div class="footer-links"><a class="footer-item" title="免责声明" href="/disclaimer/">免责声明</a><a class="footer-item" title="隐私协议" href="/privacy/">隐私协议</a><a class="footer-item" title="Cookies" href="/cookies/">Cookies</a><a class="footer-item" title="版权协议" href="/copyright/">版权协议</a></div></div><div class="footer-group"><div class="footer-title">娱乐</div><div class="footer-links"><a class="footer-item" title="小空调" href="/air-conditioner/">小空调</a><a class="footer-item" title="围住小猫" href="/catch-cat/">围住小猫</a><a class="footer-item" title="免费图床" href="/huluxiapicture/">免费图床</a></div></div><div class="footer-group"><div class="footer-title">推荐分类</div><div class="footer-links"><a class="footer-item" title="Java" href="/categories/Java/">Java</a><a class="footer-item" title="面试" href="/categories/%E9%9D%A2%E8%AF%95/">面试</a></div></div><div class="footer-group"><div class="footer-title-group"><div class="footer-title">友链</div><a class="random-friends-btn" id="footer-random-friends-btn" href="javascript:addFriendLinksInFooter();" rel="external nofollow noreferrer" title="换一批友情链接"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i></a></div><div class="footer-links" id="friend-links-in-footer"></div></div></div><div id="workboard"><img class="workSituationImg boardsign" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-上班摸鱼中.svg" alt="距离月入25k也就还差一个大佬带我~" title="距离月入25k也就还差一个大佬带我~"><div id="runtimeTextTip"></div></div><div class="wordcount"></div><span>云少已经写了 996.7k 字，</span><span>好像写完一本我国著名的 四大名著 了！！！</span><p id="ghbdages"><a class="github-badge" target="_blank" href="https://hexo.io/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="博客框架为Hexo_v5.4.0" title="博客框架为Hexo_v5.4.0"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Frame-Hexo.svg" alt="博客框架为Hexo_v5.4.0"></a><a class="github-badge" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站使用又拍云为静态资源提供CDN加速" title="本站使用又拍云为静态资源提供CDN加速"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/CDN-%E5%8F%88%E6%8B%8D%E4%BA%91-orange%3Fstyle%3Dflat%26logo%3D%E5%8F%88%E6%8B%8D%E4%BA%91" alt="本站使用又拍云为静态资源提供CDN加速"></a><a class="github-badge" target="_blank" href="https://github.com/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站项目由Github托管" title="本站项目由Github托管"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Source-Github.svg" alt="本站项目由Github托管"></a><a class="github-badge" target="_blank" href="http://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可" title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.2.0/img/badge/Copyright-BY-NC-SA.svg" alt="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"></a><a class="github-badge" target="_blank" href="https://icp.gov.moe/?keyword=20221607" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="萌ICP备20221607号" title="萌ICP备20221607号"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/%E8%90%8CICP%E5%A4%87-20221607-fe1384?style-flat&amp;logo=" alt="萌ICP备20221607号"></a></p></div><div id="footer-bar"><div class="footer-bar-links"><div class="footer-bar-left"><div id="footer-bar-tips"><div class="copyright">&copy;2018 - 2023 By <a class="footer-bar-link" href="/" title="云少" target="_blank">云少</a></div></div><div id="footer-type-tips"></div><div class="js-pjax"><script>function subtitleType(){fetch("https://v1.hitokoto.cn").then(t=>t.json()).then(t=>{var e="出自 "+t.from,p=[];p.unshift(t.hitokoto,e),window.typed=new Typed("#footer-type-tips",{strings:p,startDelay:300,typeSpeed:150,loop:!0,backSpeed:50})})}"function"==typeof Typed?subtitleType():getScript("https://cdn.cbd.int/typed.js@2.0.15/dist/typed.umd.js").then(subtitleType)</script></div></div><div class="footer-bar-right"><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="云少">云少</a><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://beian.miit.gov.cn/" title="鄂ICP备2021021095号-2">鄂ICP备2021021095号-2</a><a class="footer-bar-link cc" href="/copyright" title="cc协议"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-by-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nc-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nd-line"></i></a></div></div></div></footer></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="sidebar-site-data site-data is-center"><a href="/archives/" title="archive"><div class="headline">文章</div><div class="length-num">861</div></a><a href="/tags/" title="tag"><div class="headline">标签</div><div class="length-num">35</div></a><a href="/categories/" title="category"><div class="headline">分类</div><div class="length-num">6</div></a></div><span class="sidebar-menu-item-title">功能</span><div class="sidebar-menu-item"><a class="darkmode_switchbutton menu-child" href="javascript:void(0);" rel="external nofollow noreferrer" title="显示模式"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span>显示模式</span></a></div><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div><span class="sidebar-menu-item-title">标签</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:.88rem;color:#758692">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:.88rem;color:#2584bf">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:.88rem;color:#bfad3e">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:.88rem;color:#b9b609">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:.88rem;color:#14c83c">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:.88rem;color:#0a1878">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:.88rem;color:#625421">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:.88rem;color:#aa71bf">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:.88rem;color:#ad8774">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:.88rem;color:#19704c">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:.88rem;color:#7e5c78">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:.88rem;color:#700769">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:.88rem;color:#2b8671">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:.88rem;color:#1b0694">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:.88rem;color:#36202d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:.88rem;color:#643f76">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:.88rem;color:#1e4c38">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:.88rem;color:#a5a097">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:.88rem;color:#05682d">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:.88rem;color:#38ad2a">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:.88rem;color:#827a6b">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:.88rem;color:#4a3ac7">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:.88rem;color:#b89c14">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:.88rem;color:#922401">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:.88rem;color:#437d09">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:.88rem;color:#705b06">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:.88rem;color:#06a544">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:.88rem;color:#c86c4f">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:.88rem;color:#560b2c">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:.88rem;color:#4e0a32">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:.88rem;color:#230f8e">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:.88rem;color:#c0771e">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:.88rem;color:#b40caa">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:.88rem;color:#67389a">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:.88rem;color:#57425c">面试<sup>11</sup></a></div></div><hr></div></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="anzhiyufont anzhiyu-icon-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="anzhiyufont anzhiyu-icon-gear"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="anzhiyufont anzhiyu-icon-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="anzhiyufont anzhiyu-icon-comments"></i></a><button type="button" title="切换背景" onclick="toggleWinbox()"><i class="fas fa-display"></i></button><button id="go-up" type="button" title="回到顶部"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><button class="search-close-button"><i class="anzhiyufont anzhiyu-icon-xmark"></i></button></nav><div class="search-wrap"><div id="algolia-search-input"></div><hr><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-info"><div class="algolia-stats"></div><div class="algolia-poweredBy"></div></div></div></div></div><div id="search-mask"></div></div><div id="rightMenu"><div class="rightMenu-group rightMenu-small"><div class="rightMenu-item" id="menu-backward"><i class="anzhiyufont anzhiyu-icon-arrow-left"></i></div><div class="rightMenu-item" id="menu-forward"><i class="anzhiyufont anzhiyu-icon-arrow-right"></i></div><div class="rightMenu-item" id="menu-refresh"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right" style="font-size:1rem"></i></div><div class="rightMenu-item" id="menu-top"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></div></div><div class="rightMenu-group rightMenu-line rightMenuPlugin"><div class="rightMenu-item" id="menu-copytext"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制选中文本</span></div><div class="rightMenu-item" id="menu-pastetext"><i class="anzhiyufont anzhiyu-icon-paste"></i><span>粘贴文本</span></div><a class="rightMenu-item" id="menu-commenttext"><i class="anzhiyufont anzhiyu-icon-comment-medical"></i><span>引用到评论</span></a><div class="rightMenu-item" id="menu-newwindow"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开</span></div><div class="rightMenu-item" id="menu-copylink"><i class="anzhiyufont anzhiyu-icon-link"></i><span>复制链接地址</span></div><div class="rightMenu-item" id="menu-copyimg"><i class="anzhiyufont anzhiyu-icon-images"></i><span>复制此图片</span></div><div class="rightMenu-item" id="menu-downloadimg"><i class="anzhiyufont anzhiyu-icon-download"></i><span>下载此图片</span></div><div class="rightMenu-item" id="menu-newwindowimg"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开图片</span></div><div class="rightMenu-item" id="menu-search"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>站内搜索</span></div><div class="rightMenu-item" id="menu-searchBaidu"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>百度搜索</span></div><div class="rightMenu-item" id="menu-music-toggle"><i class="anzhiyufont anzhiyu-icon-play"></i><span>播放音乐</span></div><div class="rightMenu-item" id="menu-music-back"><i class="anzhiyufont anzhiyu-icon-backward"></i><span>切换到上一首</span></div><div class="rightMenu-item" id="menu-music-forward"><i class="anzhiyufont anzhiyu-icon-forward"></i><span>切换到下一首</span></div><div class="rightMenu-item" id="menu-music-playlist" onclick="window.open(&quot;https://y.qq.com/n/ryqq/playlist/8802438608&quot;, &quot;_blank&quot;);" style="display:none"><i class="anzhiyufont anzhiyu-icon-radio"></i><span>查看所有歌曲</span></div><div class="rightMenu-item" id="menu-music-copyMusicName"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制歌名</span></div></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item menu-link" id="menu-randomPost"><i class="anzhiyufont anzhiyu-icon-shuffle"></i><span>随便逛逛</span></a><a class="rightMenu-item menu-link" href="/categories/"><i class="anzhiyufont anzhiyu-icon-cube"></i><span>博客分类</span></a><a class="rightMenu-item menu-link" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags"></i><span>文章标签</span></a></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item" id="menu-copy" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制地址</span></a><a class="rightMenu-item" id="menu-commentBarrage" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-message"></i><span class="menu-commentBarrage-text">关闭热评</span></a><a class="rightMenu-item" id="menu-darkmode" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span class="menu-darkmode-text">深色模式</span></a><a class="rightMenu-item" id="menu-translate" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-language"></i><span>轉為繁體</span></a></div></div><div id="rightmenu-mask"></div><div id="he-plugin-simple"></div><script>var WIDGET={CONFIG:{modules:"0124",background:"2",tmpColor:"FFFFFF",tmpSize:"16",cityColor:"FFFFFF",citySize:"16",aqiColor:"E8D87B",aqiSize:"16",weatherIconSize:"24",alertIconSize:"18",padding:"10px 10px 10px 10px",shadow:"0",language:"auto",borderRadius:"20",fixed:"true",vertical:"top",horizontal:"left",left:"20",top:"7.1",key:"df245676fb434a0691ead1c63341cd94"}}</script><link rel="stylesheet" href="https://widget.qweather.net/simple/static/css/he-simple.css?v=1.4.0"><script src="https://widget.qweather.net/simple/static/js/he-simple.js?v=1.4.0"></script><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.umd.js"></script><script src="https://cdn.cbd.int/instant.page@5.2.0/instantpage.js" type="module"></script><script src="https://cdn.cbd.int/vanilla-lazyload@17.8.4/dist/lazyload.iife.min.js"></script><script src="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.js"></script><script>function panguFn(){"object"==typeof pangu?pangu.autoSpacingPage():getScript("https://cdn.cbd.int/pangu@4.0.7/dist/browser/pangu.min.js").then(()=>{pangu.autoSpacingPage()})}function panguInit(){panguFn()}document.addEventListener("DOMContentLoaded",panguInit)</script><script>var meting_api="https://api.injahow.cn/meting/?server=:server&type=:type&id=:id&auth=:auth&r=:r"</script><canvas id="universe"></canvas><script async src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/dark/dark.js"></script><script>var HoldLog=console.log;console.log=function(){};let now1=new Date;queueMicrotask(()=>{function o(){HoldLog.apply(console,arguments)}var c=new Date("09/01/2018 00:00:00"),c=(now1.setTime(now1.getTime()+250),(now1-c)/1e3/60/60/24),c=["欢迎使用安知鱼!","生活明朗, 万物可爱",`
        
       █████╗ ███╗   ██╗███████╗██╗  ██╗██╗██╗   ██╗██╗   ██╗
      ██╔══██╗████╗  ██║╚══███╔╝██║  ██║██║╚██╗ ██╔╝██║   ██║
      ███████║██╔██╗ ██║  ███╔╝ ███████║██║ ╚████╔╝ ██║   ██║
      ██╔══██║██║╚██╗██║ ███╔╝  ██╔══██║██║  ╚██╔╝  ██║   ██║
      ██║  ██║██║ ╚████║███████╗██║  ██║██║   ██║   ╚██████╔╝
      ╚═╝  ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝    ╚═════╝
        
        `,"已上线",Math.floor(c),"天","©2018 By 安知鱼 V1.6.6"],e=["NCC2-036","调用前置摄像头拍照成功，识别为【小笨蛋】.","Photo captured: ","🤪"];setTimeout(o.bind(console,`
%c${c[0]} %c ${c[1]} %c ${c[2]} %c${c[3]}%c ${c[4]}%c ${c[5]}

%c ${c[6]}
`,"color:#425AEF","","color:#425AEF","color:#425AEF","","color:#425AEF","")),setTimeout(o.bind(console,`%c ${e[0]} %c ${e[1]} %c 
${e[2]} %c
${e[3]}
`,"color:white; background-color:#4fd953","","",'background:url("https://npm.elemecdn.com/anzhiyu-blog@1.1.6/img/post/common/tinggge.gif") no-repeat;font-size:450%')),setTimeout(o.bind(console,"%c WELCOME %c 你好，小笨蛋.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c ⚡ Powered by 安知鱼 %c 你正在访问 云少 的博客.","color:white; background-color:#f0ad4e","")),setTimeout(o.bind(console,"%c W23-12 %c 你已打开控制台.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c S013-782 %c 你现在正处于监控中.","color:white; background-color:#d9534f",""))})</script><script async src="/anzhiyu/random.js"></script><script async>!function(){var n,o,r,a,i,e=new Date("09/01/2018 00:00:00"),l=new Date;setInterval(()=>{var t;if(l=new Date,i=l.getHours(),t=(l-e)/1e3/60/60/24,n=Math.floor(t),t=(l-e)/1e3/60/60-24*n,o=Math.floor(t),1==String(o).length&&(o="0"+o),t=(l-e)/1e3/60-1440*n-60*o,r=Math.floor(t),1==String(r).length&&(r="0"+r),t=(l-e)/1e3-86400*n-3600*o-60*r,a=Math.round(t),1==String(a).length&&(a="0"+a),document.getElementById("footer")){let e="";e=(i<18&&9<=i||((t=document.querySelector("#workboard .workSituationImg")).src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-下班啦.svg",t.title="下班了就该开开心心的玩耍，嘿嘿~",t.alt="下班了就该开开心心的玩耍，嘿嘿~"),`本站居然运行了 ${n} 天<span id='runtime'> ${o} 小时 ${r} 分 ${a} 秒 </span><i class='anzhiyufont anzhiyu-icon-heartbeat' style='color:red'></i>`),document.getElementById("runtimeTextTip")&&(document.getElementById("runtimeTextTip").innerHTML=e)}},1e3)}()</script><script src="https://cdn.cbd.int/algoliasearch@4.18.0/dist/algoliasearch-lite.umd.js"></script><script src="https://cdn.cbd.int/instantsearch.js@4.56.5/dist/instantsearch.production.min.js"></script><script src="/js/search/algolia.js"></script><div class="js-pjax"><script>(()=>{const e=document.querySelectorAll("#article-container .mermaid-wrap");if(0!==e.length){const n=()=>{window.loadMermaid=!0;const a="dark"===document.documentElement.getAttribute("data-theme")?"dark":"default";Array.from(e).forEach((e,t)=>{const n=e.firstElementChild;e="%%{init:{ 'theme':'"+a+"'}}%%\n"+n.textContent;const d=mermaid.render("mermaid-"+t,e);"string"==typeof d?(t=d,n.insertAdjacentHTML("afterend",t)):d.then(({svg:e})=>{n.insertAdjacentHTML("afterend",e)})})};var t=()=>{window.loadMermaid?n():getScript("https://cdn.cbd.int/mermaid@10.2.4/dist/mermaid.min.js").then(n)};anzhiyu.addGlobalFn("themeChange",n,"mermaid"),window.pjax?t():document.addEventListener("DOMContentLoaded",t)}})()</script><script>(()=>{const t=()=>{twikoo.init(Object.assign({el:"#twikoo-wrap",envId:"https://twikoo.tryrun.top/",region:"",onCommentLoaded:()=>{anzhiyu.loadLightbox(document.querySelectorAll("#twikoo .tk-content img:not(.tk-owo-emotion)"))}},null))};var o=()=>{"object"==typeof twikoo?setTimeout(n,0):getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(n)};const n=()=>{t()};anzhiyu.loadComment(document.getElementById("twikoo-wrap"),o)})()</script><script>(()=>{const t=()=>{window.artalkItem=new Artalk(Object.assign({el:"#artalk-wrap",server:"https://artalk.tryrun.top/",site:"https://blog.tryrun.top/",pageKey:location.pathname,darkMode:"dark"===document.documentElement.getAttribute("data-theme"),countEl:".artalk-count"},null)),"null"!==GLOBAL_CONFIG.lightbox&&window.artalkItem.use(t=>{t.on("list-loaded",()=>{t.getCommentList().forEach(t=>{t=t.getRender().$content;anzhiyu.loadLightbox(t.querySelectorAll("img:not([atk-emoticon])"))})})})};var a=async()=>{"object"==typeof window.artalkItem||(await getCSS("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.css"),await getScript("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.js")),t()};anzhiyu.addGlobalFn("themeChange",t=>{var a=document.getElementById("artalk-wrap");a&&a.children.length&&window.artalkItem.setDarkMode("dark"===t)},"artalk"),window.loadOtherComment=a})()</script><input type="hidden" name="page-type" id="page-type" value="post"></div><script>window.addEventListener("load",()=>{const t=e=>e=""!==e&&150<(e=(e=(e=(e=e.replace(/<img.*?src="(.*?)"?[^\>]+>/gi,"[图片]")).replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi,"[链接]")).replace(/<pre><code>.*?<\/pre>/gi,"[代码]")).replace(/<[^>]+>/g,"")).length?e.substring(0,150)+"...":e,n=t=>{let n="";if(t.length)for(let e=0;e<t.length;e++)n=(n=(n+="<div class='aside-list-item'>")+`<a href='${t[e].url}' class='thumbnail'><img data-lazy-src='${t[e].avatar}' alt='${t[e].nick}'><div class='name'><span>${t[e].nick} </span></div></a>`)+`<div class='content'>
        <a class='comment' href='${t[e].url}' title='${t[e].content}'>${t[e].content}</a>
        <time datetime="${t[e].date}">${anzhiyu.diffDate(t[e].date,!0)}</time></div>
        </div>`;else n+="没有评论";var e=document.querySelector("#card-newest-comments .aside-list");e.innerHTML=n,window.lazyLoadInstance&&window.lazyLoadInstance.update(),window.pjax&&window.pjax.refresh(e)};var e=()=>{var e;document.querySelector("#card-newest-comments .aside-list")&&((e=saveToLocal.get("twikoo-newest-comments"))?n(JSON.parse(e)):(e=()=>{twikoo.getRecentComments({envId:"https://twikoo.tryrun.top/",region:"",pageSize:6,includeReply:!0}).then(function(e){e=e.map(e=>({content:t(e.comment),avatar:e.avatar,nick:e.nick,url:e.url+"#"+e.id,date:new Date(e.created).toISOString()}));saveToLocal.set("twikoo-newest-comments",JSON.stringify(e),10/1440),n(e)}).catch(function(e){document.querySelector("#card-newest-comments .aside-list").textContent="无法获取评论，请确认相关配置是否正确"})},"object"==typeof twikoo?e():getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(e)))};e(),document.addEventListener("pjax:complete",e)})</script><script async data-pjax src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.1/bubble/bubble.js"></script><script>var visitorMail="visitor@anheyu.com"</script><script async data-pjax src="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/waterfall/waterfall.js"></script><script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qrcodejs/1.0.0/qrcode.min.js"></script><script src="/js/anzhiyu/right_click_menu.js"></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.9/icon/ali_iconfont_css.css"><script async src="/js/1.min.js?1"></script><script async src="/js/bg.js?1"></script><script async src="https://cdn1.tianli0.top/npm/sweetalert2@8.19.0/dist/sweetalert2.all.js"></script><script async src="/js/lunar.min.js?1"></script><script async src="/js/day.min.js?1"></script><script async src="/js/winbox.bundle.min.js?1"></script><script id="click-show-text" src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/dist/click-show-text.min.js" data-mobile="true" data-text="富强,民主,文明,和谐,自由,平等,公正,法治,爱国,敬业,诚信,友善" data-fontsize="15px" data-random="true" async></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/aplayer/APlayer.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/anzhiyu-blog-static@1.0.1/js/APlayer.min.js"></script><script src="https://cdn.cbd.int/hexo-anzhiyu-music@1.0.1/assets/js/Meting2.min.js"></script><script src="https://cdn.cbd.int/pjax@0.2.8/pjax.min.js"></script><script>let pjaxSelectors=['meta[property="og:image"]','meta[property="og:title"]','meta[property="og:url"]','meta[property="og:type"]','meta[property="og:site_name"]','meta[property="og:description"]',"head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"];var pjax=new Pjax({elements:'a:not([target="_blank"])',selectors:pjaxSelectors,cacheBust:!1,analytics:!0,scrollRestoration:!1});document.addEventListener("pjax:send",function(){if(anzhiyu.removeGlobalFnEvent("pjax"),anzhiyu.removeGlobalFnEvent("themeChange"),document.getElementById("rightside").classList.remove("rightside-show"),window.aplayers)for(let e=0;e<window.aplayers.length;e++)window.aplayers[e].options.fixed||window.aplayers[e].destroy();"object"==typeof typed&&typed.destroy();var e=document.body.classList;e.contains("read-mode")&&e.remove("read-mode")}),document.addEventListener("pjax:complete",function(){window.refreshFn(),document.querySelectorAll("script[data-pjax]").forEach(e=>{const t=document.createElement("script");var a=e.text||e.textContent||e.innerHTML||"";Array.from(e.attributes).forEach(e=>t.setAttribute(e.name,e.value)),t.appendChild(document.createTextNode(a)),e.parentNode.replaceChild(t,e)}),GLOBAL_CONFIG.islazyload&&window.lazyLoadInstance.update(),"function"==typeof chatBtnFn&&chatBtnFn(),"function"==typeof panguInit&&panguInit(),"function"==typeof gtag&&gtag("config","G-3VMKW5TZBM",{page_path:window.location.pathname}),"object"==typeof _hmt&&_hmt.push(["_trackPageview",window.location.pathname]),"function"==typeof loadMeting&&document.getElementsByClassName("aplayer").length&&loadMeting(),"object"==typeof Prism&&Prism.highlightAll()}),document.addEventListener("pjax:error",e=>{404===e.request.status&&pjax.loadUrl("/404.html")})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script charset="UTF-8" src="https://cdn.cbd.int/anzhiyu-theme-static@1.1.5/accesskey/accesskey.js"></script></div><div id="popup-window"><div class="popup-window-title">通知</div><div class="popup-window-divider"></div><div class="popup-window-content"><div class="popup-tip">你好呀</div><div class="popup-link"><i class="anzhiyufont anzhiyu-icon-arrow-circle-right"></i></div></div></div></body></html>